Repository: bytecodealliance/wasmtime
Pull request #5300 (2022-11-18 22:02:11)

Cranelift: Add `heap_load` and `heap_store` instructions
As discussed in https://github.com/bytecodealliance/wasmtime/issues/5283.

I intend to implement option (3) from that issue, but this PR doesn't actually add any legalization or backend lowerings yet, just interpreter support and a suite of tests.

fitzgen: (I will also wait to remove `heap_addr` until I have everything moved over to `heap_{load,store}` and feel like we are in a good position)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 42adce13c..31643c976 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1259,6 +1259,15 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
 [[package]]
 name = "fs-set-times"
 version = "0.17.0"
@@ -1439,6 +1448,16 @@ version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
 
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "indexmap"
 version = "1.9.1"
@@ -2046,6 +2065,12 @@ dependencies = [
  "base64ct",
 ]
 
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.9"
@@ -2916,6 +2941,21 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
 [[package]]
 name = "tokio"
 version = "1.18.1"
@@ -3005,6 +3045,12 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
 [[package]]
 name = "unicode-linebreak"
 version = "0.1.2"
@@ -3014,6 +3060,15 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "unicode-normalization"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
+dependencies = [
+ "tinyvec",
+]
+
 [[package]]
 name = "unicode-width"
 version = "0.1.9"
@@ -3036,6 +3091,17 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
 [[package]]
 name = "uuid"
 version = "1.0.0"
@@ -3250,18 +3316,18 @@ checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
 
 [[package]]
 name = "wasm-encoder"
-version = "0.19.1"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9424cdab516a16d4ea03c8f4a01b14e7b2d04a129dcc2bcdde5bcc5f68f06c41"
+checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614"
 dependencies = [
  "leb128",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b48cf16265251b7e11c57686ea068a77c87a67aada5cff4ee8dd50382820f01b"
+checksum = "3a3f74c801ac9b17a797d7d2ea441277bc1ded7ba521ca5146258d6510cc2f11"
 dependencies = [
  "egg",
  "log",
@@ -3273,9 +3339,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-smith"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f351bb8188460a311bceb4e2fd7423e09ba5c4a16fc03f3b258f454c4d706881"
+checksum = "ca488f460a34d3af30f1d65f26f108612f2755872f2aba9906c36f1c046ef1cf"
 dependencies = [
  "arbitrary",
  "flagset",
@@ -3325,11 +3391,12 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.94.0"
+version = "0.95.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdac7e1d98d70913ae3b4923dd7419c8ea7bdfd4c44a240a0ba305d929b7f191"
+checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a"
 dependencies = [
  "indexmap",
+ "url",
 ]
 
 [[package]]
@@ -3343,9 +3410,9 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.43"
+version = "0.2.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c093ddb9e6526cc59d93032b9be7a8d014cc997e8a9372f394c9624f820d209"
+checksum = "ae24500f9cc27a4b2b338e66693ff53c08b17cf920bdc81e402a09fe7a204eea"
 dependencies = [
  "anyhow",
  "wasmparser",
@@ -3462,6 +3529,7 @@ version = "4.0.0"
 dependencies = [
  "anyhow",
  "async-trait",
+ "bstr",
  "clap 3.2.8",
  "component-macro-test",
  "component-test-util",
@@ -3492,7 +3560,7 @@ dependencies = [
  "wasmtime-wasi-crypto",
  "wasmtime-wasi-nn",
  "wasmtime-wast",
- "wast 49.0.0",
+ "wast 50.0.0",
  "wat",
  "windows-sys",
 ]
@@ -3700,6 +3768,7 @@ dependencies = [
  "mach",
  "memfd",
  "memoffset",
+ "once_cell",
  "paste",
  "rand 0.8.5",
  "rustix",
@@ -3761,7 +3830,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 49.0.0",
+ "wast 50.0.0",
 ]
 
 [[package]]
@@ -3786,9 +3855,9 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "49.0.0"
+version = "50.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05ef81fcd60d244cafffeafac3d17615fdb2fddda6aca18f34a8ae233353587c"
+checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34"
 dependencies = [
  "leb128",
  "memchr",
@@ -3798,11 +3867,11 @@ dependencies = [
 
 [[package]]
 name = "wat"
-version = "1.0.51"
+version = "1.0.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c347c4460ffb311e95aafccd8c29e4888f241b9e4b3bb0e0ccbd998de2c8c0d"
+checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15"
 dependencies = [
- "wast 49.0.0",
+ "wast 50.0.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 605695b9c..4054881f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,6 +63,7 @@ wasmtime-wast = { workspace = true, features = ['component-model'] }
 wasmtime-component-util = { workspace = true }
 component-macro-test = { path = "crates/misc/component-macro-test" }
 component-test-util = { workspace = true }
+bstr = "0.2.17"
 
 [target.'cfg(windows)'.dev-dependencies]
 windows-sys = { workspace = true, features = ["Win32_System_Memory"] }
@@ -154,13 +155,13 @@ winch-codegen = { path = "winch/codegen", version = "=0.2.0" }
 
 target-lexicon = { version = "0.12.3", default-features = false }
 anyhow = "1.0.22"
-wasmparser = "0.94.0"
-wat = "1.0.51"
-wast = "49.0.0"
-wasmprinter = "0.2.43"
-wasm-encoder = "0.19.1"
-wasm-smith = "0.11.8"
-wasm-mutate = "0.2.11"
+wasmparser = "0.95.0"
+wat = "1.0.52"
+wast = "50.0.0"
+wasmprinter = "0.2.44"
+wasm-encoder = "0.20.0"
+wasm-smith = "0.11.9"
+wasm-mutate = "0.2.12"
 windows-sys = "0.36.0"
 env_logger = "0.9"
 rustix = "0.35.10"
diff --git a/build.rs b/build.rs
index 91d834221..374bb83fa 100644
--- a/build.rs
+++ b/build.rs
@@ -12,6 +12,7 @@ use std::process::Command;
 
 fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=build.rs");
+
     let out_dir = PathBuf::from(
         env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
     );
@@ -43,6 +44,7 @@ fn main() -> anyhow::Result<()> {
                     "tests/spec_testsuite/proposals/multi-memory",
                     strategy,
                 )?;
+                test_directory_module(out, "tests/spec_testsuite/proposals/threads", strategy)?;
             } else {
                 println!(
                     "cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \
@@ -62,7 +64,6 @@ fn main() -> anyhow::Result<()> {
     drop(Command::new("rustfmt").arg(&output).status());
     Ok(())
 }
-
 fn test_directory_module(
     out: &mut String,
     path: impl AsRef<Path>,
@@ -91,7 +92,7 @@ fn test_directory(
                 return None;
             }
             // Ignore files starting with `.`, which could be editor temporary files
-            if p.file_stem()?.to_str()?.starts_with(".") {
+            if p.file_stem()?.to_str()?.starts_with('.') {
                 return None;
             }
             Some(p)
@@ -116,8 +117,7 @@ fn extract_name(path: impl AsRef<Path>) -> String {
         .expect("filename should have a stem")
         .to_str()
         .expect("filename should be representable as a string")
-        .replace("-", "_")
-        .replace("/", "_")
+        .replace(['-', '/'], "_")
 }
 
 fn with_test_module<T>(
@@ -163,7 +163,7 @@ fn write_testsuite_tests(
         "    crate::wast::run_wast(r#\"{}\"#, crate::wast::Strategy::{}, {}).unwrap();",
         path.display(),
         strategy,
-        pooling
+        pooling,
     )?;
     writeln!(out, "}}")?;
     writeln!(out)?;
diff --git a/cranelift/codegen/meta/src/shared/formats.rs b/cranelift/codegen/meta/src/shared/formats.rs
index 8c54c1916..e9d517561 100644
--- a/cranelift/codegen/meta/src/shared/formats.rs
+++ b/cranelift/codegen/meta/src/shared/formats.rs
@@ -16,6 +16,8 @@ pub(crate) struct Formats {
     pub(crate) float_compare: Rc<InstructionFormat>,
     pub(crate) func_addr: Rc<InstructionFormat>,
     pub(crate) heap_addr: Rc<InstructionFormat>,
+    pub(crate) heap_load: Rc<InstructionFormat>,
+    pub(crate) heap_store: Rc<InstructionFormat>,
     pub(crate) int_compare: Rc<InstructionFormat>,
     pub(crate) int_compare_imm: Rc<InstructionFormat>,
     pub(crate) int_add_trap: Rc<InstructionFormat>,
@@ -206,6 +208,17 @@ impl Formats {
                 .imm_with_name("size", &imm.uimm8)
                 .build(),
 
+            heap_load: Builder::new("HeapLoad").imm(&imm.heap_imm).value().build(),
+
+            heap_store: Builder::new("HeapStore")
+                // We have more fields for this instruction than
+                // `InstructionData` can hold without growing in size, so we
+                // push the immediates out into a side table.
+                .imm(&imm.heap_imm)
+                .value()
+                .value()
+                .build(),
+
             // Accessing a WebAssembly table.
             table_addr: Builder::new("TableAddr")
                 .imm(&entities.table)
diff --git a/cranelift/codegen/meta/src/shared/immediates.rs b/cranelift/codegen/meta/src/shared/immediates.rs
index 6e1e93347..9e9cdb196 100644
--- a/cranelift/codegen/meta/src/shared/immediates.rs
+++ b/cranelift/codegen/meta/src/shared/immediates.rs
@@ -59,6 +59,9 @@ pub(crate) struct Immediates {
     /// Flags for memory operations like `load` and `store`.
     pub memflags: OperandKind,
 
+    /// A reference to out-of-line immediates for heap accesses.
+    pub heap_imm: OperandKind,
+
     /// A trap code indicating the reason for trapping.
     ///
     /// The Rust enum type also has a `User(u16)` variant for user-provided trap codes.
@@ -182,6 +185,13 @@ impl Immediates {
             },
 
             memflags: new_imm("flags", "ir::MemFlags", "Memory operation flags"),
+
+            heap_imm: new_imm(
+                "heap_imm",
+                "ir::HeapImm",
+                "Reference to out-of-line heap access immediates",
+            ),
+
             trapcode: {
                 let mut trapcode_values = HashMap::new();
                 trapcode_values.insert("stk_ovf", "StackOverflow");
diff --git a/cranelift/codegen/meta/src/shared/instructions.rs b/cranelift/codegen/meta/src/shared/instructions.rs
index f6aad71b7..91a3099f5 100755
--- a/cranelift/codegen/meta/src/shared/instructions.rs
+++ b/cranelift/codegen/meta/src/shared/instructions.rs
@@ -1155,6 +1155,55 @@ pub(crate) fn define(
         .operands_out(vec![addr]),
     );
 
+    let heap_imm = &Operand::new("heap_imm", &imm.heap_imm);
+    let index =
+        &Operand::new("index", HeapOffset).with_doc("Dynamic index (in bytes) into the heap");
+    let a = &Operand::new("a", Mem).with_doc("The value loaded from the heap");
+
+    ig.push(
+        Inst::new(
+            "heap_load",
+            r#"
+            Load a value from the given heap at address ``index + offset``,
+            trapping on out-of-bounds accesses.
+
+            Checks that ``index + offset .. index + offset + sizeof(a)`` is
+            within the heap's bounds, trapping if it is not. Otherwise, when
+            that range is in bounds, loads the value from the heap.
+
+            Traps on ``index + offset + sizeof(a)`` overflow.
+            "#,
+            &formats.heap_load,
+        )
+        .operands_in(vec![heap_imm, index])
+        .operands_out(vec![a])
+        .can_load(true)
+        .can_trap(true),
+    );
+
+    let a = &Operand::new("a", Mem).with_doc("The value stored into the heap");
+
+    ig.push(
+        Inst::new(
+            "heap_store",
+            r#"
+            Store ``a`` into the given heap at address ``index + offset``,
+            trapping on out-of-bounds accesses.
+
+            Checks that ``index + offset .. index + offset + sizeof(a)`` is
+            within the heap's bounds, trapping if it is not. Otherwise, when
+            that range is in bounds, stores the value into the heap.
+
+            Traps on ``index + offset + sizeof(a)`` overflow.
+            "#,
+            &formats.heap_store,
+        )
+        .operands_in(vec![heap_imm, index, a])
+        .operands_out(vec![])
+        .can_store(true)
+        .can_trap(true),
+    );
+
     // Note this instruction is marked as having other side-effects, so GVN won't try to hoist it,
     // which would result in it being subject to spilling. While not hoisting would generally hurt
     // performance, since a computed value used many times may need to be regenerated before each
diff --git a/cranelift/codegen/src/ir/dfg.rs b/cranelift/codegen/src/ir/dfg.rs
index e0818df11..48eb4fa91 100644
--- a/cranelift/codegen/src/ir/dfg.rs
+++ b/cranelift/codegen/src/ir/dfg.rs
@@ -4,11 +4,12 @@ use crate::entity::{self, PrimaryMap, SecondaryMap};
 use crate::ir;
 use crate::ir::builder::ReplaceBuilder;
 use crate::ir::dynamic_type::{DynamicTypeData, DynamicTypes};
+use crate::ir::immediates::HeapImmData;
 use crate::ir::instructions::{BranchInfo, CallInfo, InstructionData};
 use crate::ir::{types, ConstantData, ConstantPool, Immediate};
 use crate::ir::{
-    Block, DynamicType, FuncRef, Inst, SigRef, Signature, Type, Value, ValueLabelAssignments,
-    ValueList, ValueListPool,
+    Block, DynamicType, FuncRef, HeapImm, Inst, SigRef, Signature, Type, Value,
+    ValueLabelAssignments, ValueList, ValueListPool,
 };
 use crate::ir::{ExtFuncData, RelSourceLoc};
 use crate::packed_option::ReservedValue;
@@ -83,6 +84,9 @@ pub struct DataFlowGraph {
 
     /// Stores large immediates that otherwise will not fit on InstructionData
     pub immediates: PrimaryMap<Immediate, ConstantData>,
+
+    /// Out-of-line heap access immediates that don't fit in `InstructionData`.
+    pub heap_imms: PrimaryMap<HeapImm, HeapImmData>,
 }
 
 impl DataFlowGraph {
@@ -101,6 +105,7 @@ impl DataFlowGraph {
             values_labels: None,
             constants: ConstantPool::new(),
             immediates: PrimaryMap::new(),
+            heap_imms: PrimaryMap::new(),
         }
     }
 
diff --git a/cranelift/codegen/src/ir/entities.rs b/cranelift/codegen/src/ir/entities.rs
index 315ea2183..dc63d0ec3 100644
--- a/cranelift/codegen/src/ir/entities.rs
+++ b/cranelift/codegen/src/ir/entities.rs
@@ -394,6 +394,34 @@ impl Heap {
     }
 }
 
+/// An opaque reference to some out-of-line immediates for `heap_{load,store}`
+/// instructions.
+///
+/// These immediates are too large to store in
+/// [`InstructionData`](super::instructions::InstructionData) and therefore must
+/// be tracked separately in
+/// [`DataFlowGraph::heap_imms`](super::dfg::DataFlowGraph). `HeapImm` provides
+/// a way to reference values stored there.
+///
+/// While the order is stable, it is arbitrary.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
+pub struct HeapImm(u32);
+entity_impl!(HeapImm, "heap_imm");
+
+impl HeapImm {
+    /// Create a new `HeapImm` reference from its number.
+    ///
+    /// This method is for use by the parser.
+    pub fn with_number(n: u32) -> Option<Self> {
+        if n < u32::MAX {
+            Some(Self(n))
+        } else {
+            None
+        }
+    }
+}
+
 /// An opaque reference to a [WebAssembly
 /// table](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#WebAssembly_tables).
 ///
diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs
index 3b3f70323..54f737b19 100644
--- a/cranelift/codegen/src/ir/immediates.rs
+++ b/cranelift/codegen/src/ir/immediates.rs
@@ -4,6 +4,7 @@
 //! Each type here should have a corresponding definition in the
 //! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language.
 
+use crate::ir;
 use alloc::vec::Vec;
 use core::cmp::Ordering;
 use core::convert::TryFrom;
@@ -1177,6 +1178,18 @@ impl Not for Ieee64 {
     }
 }
 
+/// Out-of-line heap access immediates.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
+pub struct HeapImmData {
+    /// The memory flags for the heap access.
+    pub flags: ir::MemFlags,
+    /// The heap being accessed.
+    pub heap: ir::Heap,
+    /// The static offset added to the heap access's index.
+    pub offset: Uimm32,
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/cranelift/codegen/src/ir/mod.rs b/cranelift/codegen/src/ir/mod.rs
index 52a4b6037..23f952738 100644
--- a/cranelift/codegen/src/ir/mod.rs
+++ b/cranelift/codegen/src/ir/mod.rs
@@ -37,8 +37,8 @@ pub use crate::ir::constant::{ConstantData, ConstantPool};
 pub use crate::ir::dfg::{DataFlowGraph, ValueDef};
 pub use crate::ir::dynamic_type::{dynamic_to_fixed, DynamicTypeData, DynamicTypes};
 pub use crate::ir::entities::{
-    Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Heap, Immediate, Inst,
-    JumpTable, SigRef, StackSlot, Table, UserExternalNameRef, Value,
+    Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Heap, HeapImm, Immediate,
+    Inst, JumpTable, SigRef, StackSlot, Table, UserExternalNameRef, Value,
 };
 pub use crate::ir::extfunc::{
     AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature,
diff --git a/cranelift/codegen/src/isa/aarch64/inst/mod.rs b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
index 7e7c76f57..2acef7e19 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
@@ -1142,6 +1142,7 @@ impl MachInst for Inst {
         // See the note in [crate::isa::aarch64::abi::is_caller_save_reg] for
         // more information on this ABI-implementation hack.
         match self {
+            &Inst::Args { .. } => false,
             &Inst::Call { ref info } => info.caller_callconv != info.callee_callconv,
             &Inst::CallInd { ref info } => info.caller_callconv != info.callee_callconv,
             _ => true,
diff --git a/cranelift/codegen/src/isa/aarch64/lower_inst.rs b/cranelift/codegen/src/isa/aarch64/lower_inst.rs
index 1fcf7446b..433db8407 100644
--- a/cranelift/codegen/src/isa/aarch64/lower_inst.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower_inst.rs
@@ -139,8 +139,8 @@ pub(crate) fn lower_insn_to_regs(
             panic!("Direct stack memory access not supported; should not be used by Wasm");
         }
 
-        Opcode::HeapAddr => {
-            panic!("heap_addr should have been removed by legalization!");
+        Opcode::HeapLoad | Opcode::HeapStore | Opcode::HeapAddr => {
+            panic!("heap access instructions should have been removed by legalization!");
         }
 
         Opcode::TableAddr => {
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 9cb7c8c50..f5a663f8c 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -657,7 +657,10 @@ impl MachInst for Inst {
     }
 
     fn is_included_in_clobbers(&self) -> bool {
-        true
+        match self {
+            &Inst::Args { .. } => false,
+            _ => true,
+        }
     }
 
     fn is_args(&self) -> bool {
diff --git a/cranelift/codegen/src/isa/s390x/inst/mod.rs b/cranelift/codegen/src/isa/s390x/inst/mod.rs
index 4c8d27a36..6ca7adadc 100644
--- a/cranelift/codegen/src/isa/s390x/inst/mod.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/mod.rs
@@ -1109,6 +1109,7 @@ impl MachInst for Inst {
         // half-caller-save, half-callee-save SysV ABI for some vector
         // registers.
         match self {
+            &Inst::Args { .. } => false,
             &Inst::Call { ref info, .. } => info.caller_callconv != info.callee_callconv,
             &Inst::CallInd { ref info, .. } => info.caller_callconv != info.callee_callconv,
             _ => true,
diff --git a/cranelift/codegen/src/isa/s390x/lower.rs b/cranelift/codegen/src/isa/s390x/lower.rs
index cfc73c7f6..79765d017 100644
--- a/cranelift/codegen/src/isa/s390x/lower.rs
+++ b/cranelift/codegen/src/isa/s390x/lower.rs
@@ -212,8 +212,8 @@ impl LowerBackend for S390xBackend {
             Opcode::StackLoad | Opcode::StackStore => {
                 panic!("Direct stack memory access not supported; should not be used by Wasm");
             }
-            Opcode::HeapAddr => {
-                panic!("heap_addr should have been removed by legalization!");
+            Opcode::HeapLoad | Opcode::HeapStore | Opcode::HeapAddr => {
+                panic!("heap access instructions should have been removed by legalization!");
             }
             Opcode::TableAddr => {
                 panic!("table_addr should have been removed by legalization!");
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index f9261f1e9..4704f809b 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -2236,6 +2236,13 @@ impl MachInst for Inst {
         }
     }
 
+    fn is_included_in_clobbers(&self) -> bool {
+        match self {
+            &Inst::Args { .. } => false,
+            _ => true,
+        }
+    }
+
     fn is_args(&self) -> bool {
         match self {
             Self::Args { .. } => true,
diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs
index da0fb6efa..f5ea23c0a 100644
--- a/cranelift/codegen/src/isa/x64/lower.rs
+++ b/cranelift/codegen/src/isa/x64/lower.rs
@@ -549,8 +549,8 @@ fn lower_insn_to_regs(
             panic!("global_value should have been removed by legalization!");
         }
 
-        Opcode::HeapAddr => {
-            panic!("heap_addr should have been removed by legalization!");
+        Opcode::HeapLoad | Opcode::HeapStore | Opcode::HeapAddr => {
+            panic!("heap access instructions should have been removed by legalization!");
         }
 
         Opcode::TableAddr => {
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index acfd22f6a..f83b59ea8 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -1396,6 +1396,7 @@ impl<M: ABIMachineSpec> Callee<M> {
         sigs: &SigSet,
         idx: usize,
         into_regs: ValueRegs<Writable<Reg>>,
+        vregs: &mut VRegAllocator<M::I>,
     ) -> SmallInstVec<M::I> {
         let mut insts = smallvec![];
         let mut copy_arg_slot_to_reg = |slot: &ABIArgSlot, into_reg: &Writable<Reg>| {
@@ -1470,7 +1471,14 @@ impl<M: ABIMachineSpec> Callee<M> {
                 let into_reg = into_regs.only_reg().unwrap();
                 // We need to dereference the pointer.
                 let base = match &pointer {
-                    &ABIArgSlot::Reg { reg, .. } => Reg::from(reg),
+                    &ABIArgSlot::Reg { reg, ty, .. } => {
+                        let tmp = vregs.alloc(ty).unwrap().only_reg().unwrap();
+                        self.reg_args.push(ArgPair {
+                            vreg: Writable::from_reg(tmp),
+                            preg: reg.into(),
+                        });
+                        tmp
+                    }
                     &ABIArgSlot::Stack { offset, ty, .. } => {
                         // In this case we need a temp register to hold the address.
                         // This was allocated in the `init` routine.
@@ -1606,12 +1614,17 @@ impl<M: ABIMachineSpec> Callee<M> {
     /// values or an otherwise large return value that must be passed on the
     /// stack; typically the ABI specifies an extra hidden argument that is a
     /// pointer to that memory.
-    pub fn gen_retval_area_setup(&mut self, sigs: &SigSet) -> Option<M::I> {
+    pub fn gen_retval_area_setup(
+        &mut self,
+        sigs: &SigSet,
+        vregs: &mut VRegAllocator<M::I>,
+    ) -> Option<M::I> {
         if let Some(i) = sigs[self.sig].stack_ret_arg {
             let insts = self.gen_copy_arg_to_regs(
                 sigs,
                 i.into(),
                 ValueRegs::one(self.ret_area_ptr.unwrap()),
+                vregs,
             );
             insts.into_iter().next().map(|inst| {
                 trace!(
diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs
index 97fcf909e..2cc2b9fe3 100644
--- a/cranelift/codegen/src/machinst/lower.rs
+++ b/cranelift/codegen/src/machinst/lower.rs
@@ -573,7 +573,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
                     .vcode
                     .vcode
                     .abi
-                    .gen_copy_arg_to_regs(&self.vcode.vcode.sigs, i, regs)
+                    .gen_copy_arg_to_regs(&self.vcode.vcode.sigs, i, regs, &mut self.vregs)
                     .into_iter()
                 {
                     self.emit(insn);
@@ -601,7 +601,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
                 .vcode
                 .vcode
                 .abi
-                .gen_retval_area_setup(&self.vcode.vcode.sigs)
+                .gen_retval_area_setup(&self.vcode.vcode.sigs, &mut self.vregs)
             {
                 self.emit(insn);
             }
diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs
index fba277b89..ec3fdb356 100644
--- a/cranelift/codegen/src/machinst/mod.rs
+++ b/cranelift/codegen/src/machinst/mod.rs
@@ -104,9 +104,7 @@ pub trait MachInst: Clone + Debug {
     fn is_args(&self) -> bool;
 
     /// Should this instruction be included in the clobber-set?
-    fn is_included_in_clobbers(&self) -> bool {
-        true
-    }
+    fn is_included_in_clobbers(&self) -> bool;
 
     /// Generate a move.
     fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self;
diff --git a/cranelift/codegen/src/opts.rs b/cranelift/codegen/src/opts.rs
index a64f32c0d..7e77370e2 100644
--- a/cranelift/codegen/src/opts.rs
+++ b/cranelift/codegen/src/opts.rs
@@ -9,8 +9,8 @@ pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64,
 pub use crate::ir::types::*;
 pub use crate::ir::{
     dynamic_to_fixed, AtomicRmwOp, Block, Constant, DynamicStackSlot, FuncRef, GlobalValue, Heap,
-    Immediate, InstructionImms, JumpTable, MemFlags, Opcode, StackSlot, Table, TrapCode, Type,
-    Value,
+    HeapImm, Immediate, InstructionImms, JumpTable, MemFlags, Opcode, StackSlot, Table, TrapCode,
+    Type, Value,
 };
 use crate::isle_common_prelude_methods;
 use crate::machinst::isle::*;
diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs
index d47817e09..535fe0d99 100644
--- a/cranelift/codegen/src/verifier/mod.rs
+++ b/cranelift/codegen/src/verifier/mod.rs
@@ -64,6 +64,7 @@ use crate::entity::SparseSet;
 use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
 use crate::ir;
 use crate::ir::entities::AnyEntity;
+use crate::ir::immediates::HeapImmData;
 use crate::ir::instructions::{BranchInfo, CallInfo, InstructionFormat, ResolvedConstraint};
 use crate::ir::{
     types, ArgumentPurpose, Block, Constant, DynamicStackSlot, FuncRef, Function, GlobalValue,
@@ -678,6 +679,10 @@ impl<'a> Verifier<'a> {
             UnaryGlobalValue { global_value, .. } => {
                 self.verify_global_value(inst, global_value, errors)?;
             }
+            HeapLoad { heap_imm, .. } | HeapStore { heap_imm, .. } => {
+                let HeapImmData { heap, .. } = self.func.dfg.heap_imms[heap_imm];
+                self.verify_heap(inst, heap, errors)?;
+            }
             HeapAddr { heap, .. } => {
                 self.verify_heap(inst, heap, errors)?;
             }
diff --git a/cranelift/codegen/src/write.rs b/cranelift/codegen/src/write.rs
index 725ca46c4..58d73e4ba 100644
--- a/cranelift/codegen/src/write.rs
+++ b/cranelift/codegen/src/write.rs
@@ -5,6 +5,7 @@
 
 use crate::entity::SecondaryMap;
 use crate::ir::entities::AnyEntity;
+use crate::ir::immediates::{HeapImmData, Uimm32};
 use crate::ir::{Block, DataFlowGraph, Function, Inst, SigRef, Type, Value, ValueDef};
 use crate::packed_option::ReservedValue;
 use alloc::string::{String, ToString};
@@ -476,6 +477,47 @@ pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt
             dynamic_stack_slot,
             ..
         } => write!(w, " {}, {}", arg, dynamic_stack_slot),
+        HeapLoad {
+            opcode: _,
+            heap_imm,
+            arg,
+        } => {
+            let HeapImmData {
+                flags,
+                heap,
+                offset,
+            } = dfg.heap_imms[heap_imm];
+            write!(
+                w,
+                " {heap} {flags} {arg}{optional_offset}",
+                optional_offset = if offset == Uimm32::from(0) {
+                    "".to_string()
+                } else {
+                    format!("+{offset}")
+                }
+            )
+        }
+        HeapStore {
+            opcode: _,
+            heap_imm,
+            args,
+        } => {
+            let HeapImmData {
+                flags,
+                heap,
+                offset,
+            } = dfg.heap_imms[heap_imm];
+            let [index, value] = args;
+            write!(
+                w,
+                " {heap} {flags} {index}{optional_offset}, {value}",
+                optional_offset = if offset == Uimm32::from(0) {
+                    "".to_string()
+                } else {
+                    format!("+{offset}")
+                }
+            )
+        }
         HeapAddr {
             heap,
             arg,
diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs
index 15c9d9e28..12c268322 100644
--- a/cranelift/interpreter/src/step.rs
+++ b/cranelift/interpreter/src/step.rs
@@ -6,6 +6,7 @@ use crate::state::{MemoryError, State};
 use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
+use cranelift_codegen::ir::immediates::HeapImmData;
 use cranelift_codegen::ir::{
     types, AbiParam, Block, ExternalName, FuncRef, Function, InstructionData, Opcode, TrapCode,
     Type, Value as ValueRef,
@@ -484,6 +485,59 @@ where
         }
         Opcode::SymbolValue => unimplemented!("SymbolValue"),
         Opcode::TlsValue => unimplemented!("TlsValue"),
+        Opcode::HeapLoad => {
+            if let InstructionData::HeapLoad { heap_imm, arg, .. } = inst {
+                let HeapImmData {
+                    flags,
+                    heap,
+                    offset,
+                } = state.get_current_function().dfg.heap_imms[heap_imm];
+                let offset = i128::from(u32::from(offset));
+                let addr_ty = state.get_current_function().dfg.value_type(arg);
+                let index = state.get_value(arg).unwrap().into_int().unwrap();
+                let load_ty = inst_context.controlling_type().unwrap();
+                let load_size = i128::from(load_ty.bytes());
+                assign_or_memtrap(AddressSize::try_from(addr_ty).and_then(|addr_size| {
+                    let heap_address = index.saturating_add(offset).saturating_add(load_size);
+                    let heap_address =
+                        u64::try_from(heap_address).map_err(|e| MemoryError::OutOfBoundsLoad {
+                            addr: Address::try_from(0).unwrap(),
+                            load_size: load_size as usize,
+                        })?;
+                    let address = state.heap_address(addr_size, heap, heap_address)?;
+                    state.checked_load(address, load_ty)
+                }))
+            } else {
+                unreachable!()
+            }
+        }
+        Opcode::HeapStore => {
+            if let InstructionData::HeapStore { heap_imm, args, .. } = inst {
+                let HeapImmData {
+                    flags,
+                    heap,
+                    offset,
+                } = state.get_current_function().dfg.heap_imms[heap_imm];
+                let offset = i128::from(u32::from(offset));
+                let addr_ty = state.get_current_function().dfg.value_type(args[0]);
+                let index = state.get_value(args[0]).unwrap().into_int().unwrap();
+                let value = state.get_value(args[1]).unwrap();
+                let store_ty = state.get_current_function().dfg.value_type(args[1]);
+                let store_size = i128::from(store_ty.bytes());
+                continue_or_memtrap(AddressSize::try_from(addr_ty).and_then(|addr_size| {
+                    let heap_address = index.saturating_add(offset).saturating_add(store_size);
+                    let heap_address =
+                        u64::try_from(heap_address).map_err(|e| MemoryError::OutOfBoundsStore {
+                            addr: Address::try_from(0).unwrap(),
+                            store_size: store_size as usize,
+                        })?;
+                    let address = state.heap_address(addr_size, heap, heap_address)?;
+                    state.checked_store(address, value)
+                }))
+            } else {
+                unreachable!()
+            }
+        }
         Opcode::HeapAddr => {
             if let InstructionData::HeapAddr {
                 heap,
diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs
index ff85acec7..baf0bc0a9 100644
--- a/cranelift/reader/src/parser.rs
+++ b/cranelift/reader/src/parser.rs
@@ -11,7 +11,9 @@ use crate::testfile::{Comment, Details, Feature, TestFile};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::entity::{EntityRef, PrimaryMap};
 use cranelift_codegen::ir::entities::{AnyEntity, DynamicType};
-use cranelift_codegen::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64};
+use cranelift_codegen::ir::immediates::{
+    HeapImmData, Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64,
+};
 use cranelift_codegen::ir::instructions::{InstructionData, InstructionFormat, VariableArgs};
 use cranelift_codegen::ir::types::INVALID;
 use cranelift_codegen::ir::types::*;
@@ -876,6 +878,19 @@ impl<'a> Parser<'a> {
         }
     }
 
+    // Match and consume an optional uimm32 offset immediate.
+    //
+    // Note that this will match an empty string as an empty offset, and that if an offset is
+    // present, it must contain a plus sign.
+    fn optional_uimm32_offset(&mut self) -> ParseResult<Uimm32> {
+        match self.token() {
+            Some(Token::Integer(x)) if x.starts_with('+') => {
+                self.match_uimm32("expected a uimm32 offset immediate")
+            }
+            _ => Ok(Uimm32::from(0)),
+        }
+    }
+
     // Match and consume a u8 immediate.
     // This is used for lane numbers in SIMD vectors.
     fn match_uimm8(&mut self, err_msg: &str) -> ParseResult<u8> {
@@ -2976,6 +2991,42 @@ impl<'a> Parser<'a> {
                     size,
                 }
             }
+            InstructionFormat::HeapLoad => {
+                let heap = self.match_heap("expected heap identifier")?;
+                ctx.check_heap(heap, self.loc)?;
+                let flags = self.optional_memflags();
+                let arg = self.match_value("expected SSA value heap index")?;
+                let offset = self.optional_uimm32_offset()?;
+                let heap_imm = ctx.function.dfg.heap_imms.push(HeapImmData {
+                    flags,
+                    heap,
+                    offset,
+                });
+                InstructionData::HeapLoad {
+                    opcode,
+                    heap_imm,
+                    arg,
+                }
+            }
+            InstructionFormat::HeapStore => {
+                let heap = self.match_heap("expected heap identifier")?;
+                ctx.check_heap(heap, self.loc)?;
+                let flags = self.optional_memflags();
+                let index = self.match_value("expected SSA value heap index")?;
+                let offset = self.optional_uimm32_offset()?;
+                self.match_token(Token::Comma, "expected ',' between operands")?;
+                let value = self.match_value("expected SSA value to store")?;
+                let heap_imm = ctx.function.dfg.heap_imms.push(HeapImmData {
+                    flags,
+                    heap,
+                    offset,
+                });
+                InstructionData::HeapStore {
+                    opcode,
+                    heap_imm,
+                    args: [index, value],
+                }
+            }
             InstructionFormat::TableAddr => {
                 let table = self.match_table("expected table identifier")?;
                 ctx.check_table(table, self.loc)?;
diff --git a/crates/c-api/include/wasmtime/val.h b/crates/c-api/include/wasmtime/val.h
index ae0a1961c..f16c7bd48 100644
--- a/crates/c-api/include/wasmtime/val.h
+++ b/crates/c-api/include/wasmtime/val.h
@@ -119,38 +119,24 @@ typedef uint8_t wasmtime_v128[16];
  */
 typedef union wasmtime_valunion {
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_I32
-  ///
-  /// Note that this field is always stored in a little-endian format.
   int32_t i32;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_I64
-  ///
-  /// Note that this field is always stored in a little-endian format.
   int64_t i64;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_F32
-  ///
-  /// Note that this field is always stored in a little-endian format.
   float32_t f32;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_F64
-  ///
-  /// Note that this field is always stored in a little-endian format.
   float64_t f64;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_FUNCREF
   ///
   /// If this value represents a `ref.null func` value then the `store_id` field
   /// is set to zero.
-  ///
-  /// Note that this field is always stored in a little-endian format.
   wasmtime_func_t funcref;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_EXTERNREF
   ///
   /// If this value represents a `ref.null extern` value then this pointer will
   /// be `NULL`.
-  ///
-  /// Note that this field is always stored in a little-endian format.
   wasmtime_externref_t *externref;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_V128
-  ///
-  /// Note that this field is always stored in a little-endian format.
   wasmtime_v128 v128;
 } wasmtime_valunion_t;
 
@@ -169,25 +155,39 @@ typedef union wasmtime_valunion {
  */
 typedef union wasmtime_val_raw {
   /// Field for when this val is a WebAssembly `i32` value.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   int32_t i32;
   /// Field for when this val is a WebAssembly `i64` value.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   int64_t i64;
   /// Field for when this val is a WebAssembly `f32` value.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   float32_t f32;
   /// Field for when this val is a WebAssembly `f64` value.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   float64_t f64;
   /// Field for when this val is a WebAssembly `v128` value.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   wasmtime_v128 v128;
   /// Field for when this val is a WebAssembly `funcref` value.
   ///
   /// If this is set to 0 then it's a null funcref, otherwise this must be
   /// passed to `wasmtime_func_from_raw` to determine the `wasmtime_func_t`.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   size_t funcref;
   /// Field for when this val is a WebAssembly `externref` value.
   ///
   /// If this is set to 0 then it's a null externref, otherwise this must be
   /// passed to `wasmtime_externref_from_raw` to determine the
   /// `wasmtime_externref_t`.
+  ///
+  /// Note that this field is always stored in a little-endian format.
   size_t externref;
 } wasmtime_val_raw_t;
 
diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs
index 1fa160362..5f6a08bf3 100644
--- a/crates/cranelift/src/compiler.rs
+++ b/crates/cranelift/src/compiler.rs
@@ -32,7 +32,7 @@ use std::sync::{Arc, Mutex};
 use wasmparser::{FuncValidatorAllocations, FunctionBody};
 use wasmtime_environ::{
     AddressMapSection, CacheStore, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionLoc,
-    InstructionAddressMap, ModuleTranslation, ModuleTypes, PtrSize, StackMapInformation, TrapCode,
+    InstructionAddressMap, ModuleTranslation, ModuleTypes, PtrSize, StackMapInformation, Trap,
     TrapEncodingBuilder, TrapInformation, Tunables, VMOffsets, WasmFunctionInfo,
 };
 
@@ -1003,18 +1003,18 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
     TrapInformation {
         code_offset: offset,
         trap_code: match code {
-            ir::TrapCode::StackOverflow => TrapCode::StackOverflow,
-            ir::TrapCode::HeapOutOfBounds => TrapCode::HeapOutOfBounds,
-            ir::TrapCode::HeapMisaligned => TrapCode::HeapMisaligned,
-            ir::TrapCode::TableOutOfBounds => TrapCode::TableOutOfBounds,
-            ir::TrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull,
-            ir::TrapCode::BadSignature => TrapCode::BadSignature,
-            ir::TrapCode::IntegerOverflow => TrapCode::IntegerOverflow,
-            ir::TrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero,
-            ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
-            ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
-            ir::TrapCode::Interrupt => TrapCode::Interrupt,
-            ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter,
+            ir::TrapCode::StackOverflow => Trap::StackOverflow,
+            ir::TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
+            ir::TrapCode::HeapMisaligned => Trap::HeapMisaligned,
+            ir::TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
+            ir::TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
+            ir::TrapCode::BadSignature => Trap::BadSignature,
+            ir::TrapCode::IntegerOverflow => Trap::IntegerOverflow,
+            ir::TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
+            ir::TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
+            ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
+            ir::TrapCode::Interrupt => Trap::Interrupt,
+            ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter,
 
             // these should never be emitted by wasmtime-cranelift
             ir::TrapCode::User(_) => unreachable!(),
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index 44764be3d..81483d0ba 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -840,14 +840,16 @@ impl<'a, 'data> Translator<'a, 'data> {
             // An imported component instance is being aliased, so the type of
             // the aliased item is directly available from the instance type.
             ComponentInstanceType::Index(ty) => {
-                self.result.push_typedef(self.types[ty].exports[name])
+                let (_url, ty) = &self.types[ty].exports[name];
+                self.result.push_typedef(*ty);
             }
 
             // An imported component was instantiated so the type of the aliased
             // export is available through the type of the export on the
             // original component.
             ComponentInstanceType::InstantiatedIndex(ty) => {
-                self.result.push_typedef(self.types[ty].exports[name])
+                let (_, ty) = self.types[ty].exports[name];
+                self.result.push_typedef(ty);
             }
 
             // A static nested component was instantiated which means that the
diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs
index 90938844b..aaa2eea70 100644
--- a/crates/environ/src/component/translate/inline.rs
+++ b/crates/environ/src/component/translate/inline.rs
@@ -668,7 +668,7 @@ impl<'a> Inliner<'a> {
                     ComponentInstanceDef::Import(path, ty) => {
                         let mut path = path.clone();
                         path.path.push(name);
-                        match self.types[*ty].exports[*name] {
+                        match self.types[*ty].exports[*name].1 {
                             TypeDef::ComponentFunc(_) => {
                                 frame.component_funcs.push(ComponentFuncDef::Import(path));
                             }
@@ -909,7 +909,7 @@ impl<'a> Inliner<'a> {
                     // Note that for now this would only work with
                     // module-exporting instances.
                     ComponentInstanceDef::Import(path, ty) => {
-                        for (name, ty) in self.types[ty].exports.iter() {
+                        for (name, (_url, ty)) in self.types[ty].exports.iter() {
                             let mut path = path.clone();
                             path.path.push(name);
                             let def = ComponentItemDef::from_import(path, *ty)?;
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index ab29a25ca..b690b9ccf 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -553,13 +553,17 @@ impl ComponentTypesBuilder {
                 ComponentTypeDeclaration::Type(ty) => self.type_declaration_type(ty)?,
                 ComponentTypeDeclaration::CoreType(ty) => self.type_declaration_core_type(ty)?,
                 ComponentTypeDeclaration::Alias(alias) => self.type_declaration_alias(alias)?,
-                ComponentTypeDeclaration::Export { name, ty } => {
+                ComponentTypeDeclaration::Export { name, url, ty } => {
                     let ty = self.component_type_ref(ty);
-                    result.exports.insert(name.to_string(), ty);
+                    result
+                        .exports
+                        .insert(name.to_string(), (url.to_string(), ty));
                 }
                 ComponentTypeDeclaration::Import(import) => {
                     let ty = self.component_type_ref(&import.ty);
-                    result.imports.insert(import.name.to_string(), ty);
+                    result
+                        .imports
+                        .insert(import.name.to_string(), (import.url.to_string(), ty));
                 }
             }
         }
@@ -581,9 +585,11 @@ impl ComponentTypesBuilder {
                 InstanceTypeDeclaration::Type(ty) => self.type_declaration_type(ty)?,
                 InstanceTypeDeclaration::CoreType(ty) => self.type_declaration_core_type(ty)?,
                 InstanceTypeDeclaration::Alias(alias) => self.type_declaration_alias(alias)?,
-                InstanceTypeDeclaration::Export { name, ty } => {
+                InstanceTypeDeclaration::Export { name, url, ty } => {
                     let ty = self.component_type_ref(ty);
-                    result.exports.insert(name.to_string(), ty);
+                    result
+                        .exports
+                        .insert(name.to_string(), (url.to_string(), ty));
                 }
             }
         }
@@ -971,9 +977,9 @@ pub struct TypeModule {
 #[derive(Serialize, Deserialize, Default)]
 pub struct TypeComponent {
     /// The named values that this component imports.
-    pub imports: IndexMap<String, TypeDef>,
+    pub imports: IndexMap<String, (String, TypeDef)>,
     /// The named values that this component exports.
-    pub exports: IndexMap<String, TypeDef>,
+    pub exports: IndexMap<String, (String, TypeDef)>,
 }
 
 /// The type of a component instance in the component model, or an instantiated
@@ -983,7 +989,7 @@ pub struct TypeComponent {
 #[derive(Serialize, Deserialize, Default)]
 pub struct TypeComponentInstance {
     /// The list of exports that this component has along with their types.
-    pub exports: IndexMap<String, TypeDef>,
+    pub exports: IndexMap<String, (String, TypeDef)>,
 }
 
 /// A component function type in the component model.
diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs
index 7fe7cec7e..a999fc9c7 100644
--- a/crates/environ/src/trap_encoding.rs
+++ b/crates/environ/src/trap_encoding.rs
@@ -2,6 +2,7 @@ use crate::obj::ELF_WASMTIME_TRAPS;
 use object::write::{Object, StandardSegment};
 use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
 use std::convert::TryFrom;
+use std::fmt;
 use std::ops::Range;
 
 /// A helper structure to build the custom-encoded section of a wasmtime
@@ -26,29 +27,30 @@ pub struct TrapInformation {
     pub code_offset: u32,
 
     /// Code of the trap.
-    pub trap_code: TrapCode,
+    pub trap_code: Trap,
 }
 
-/// A trap code describing the reason for a trap.
-///
-/// All trap instructions have an explicit trap code.
+// The code can be accessed from the c-api, where the possible values are
+// translated into enum values defined there:
+//
+// * `wasm_trap_code` in c-api/src/trap.rs, and
+// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
+//
+// These need to be kept in sync.
+#[non_exhaustive]
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
-#[repr(u8)]
-pub enum TrapCode {
+#[allow(missing_docs)]
+pub enum Trap {
     /// The current stack space was exhausted.
     StackOverflow,
 
-    /// A `heap_addr` instruction detected an out-of-bounds error.
-    ///
-    /// Note that not all out-of-bounds heap accesses are reported this way;
-    /// some are detected by a segmentation fault on the heap unmapped or
-    /// offset-guard pages.
-    HeapOutOfBounds,
+    /// An out-of-bounds memory access.
+    MemoryOutOfBounds,
 
     /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
     HeapMisaligned,
 
-    /// A `table_addr` instruction detected an out-of-bounds error.
+    /// An out-of-bounds access to a table.
     TableOutOfBounds,
 
     /// Indirect call to a null table entry.
@@ -70,15 +72,50 @@ pub enum TrapCode {
     UnreachableCodeReached,
 
     /// Execution has potentially run too long and may be interrupted.
-    /// This trap is resumable.
     Interrupt,
 
-    /// Used for the component model when functions are lifted/lowered in a way
-    /// that generates a function that always traps.
+    /// When the `component-model` feature is enabled this trap represents a
+    /// function that was `canon lift`'d, then `canon lower`'d, then called.
+    /// This combination of creation of a function in the component model
+    /// generates a function that always traps and, when called, produces this
+    /// flavor of trap.
     AlwaysTrapAdapter,
+
+    /// When wasm code is configured to consume fuel and it runs out of fuel
+    /// then this trap will be raised.
+    OutOfFuel,
+
+    /// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
+    AtomicWaitNonSharedMemory,
     // if adding a variant here be sure to update the `check!` macro below
 }
 
+impl fmt::Display for Trap {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use Trap::*;
+
+        let desc = match self {
+            StackOverflow => "call stack exhausted",
+            MemoryOutOfBounds => "out of bounds memory access",
+            HeapMisaligned => "unaligned atomic",
+            TableOutOfBounds => "undefined element: out of bounds table access",
+            IndirectCallToNull => "uninitialized element",
+            BadSignature => "indirect call type mismatch",
+            IntegerOverflow => "integer overflow",
+            IntegerDivisionByZero => "integer divide by zero",
+            BadConversionToInteger => "invalid conversion to integer",
+            UnreachableCodeReached => "wasm `unreachable` instruction executed",
+            Interrupt => "interrupt",
+            AlwaysTrapAdapter => "degenerate component adapter called",
+            OutOfFuel => "all fuel consumed by WebAssembly",
+            AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
+        };
+        write!(f, "wasm trap: {desc}")
+    }
+}
+
+impl std::error::Error for Trap {}
+
 impl TrapEncodingBuilder {
     /// Appends trap information about a function into this section.
     ///
@@ -136,7 +173,7 @@ impl TrapEncodingBuilder {
 /// The `section` provided is expected to have been built by
 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
 /// offset within the text section of the compilation image.
-pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<TrapCode> {
+pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
     let mut section = Bytes(section);
     // NB: this matches the encoding written by `append_to` above.
     let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
@@ -164,16 +201,16 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<TrapCode> {
     // FIXME: this could use some sort of derive-like thing to avoid having to
     // deduplicate the names here.
     //
-    // This simply converts from the `trap`, a `u8`, to the `TrapCode` enum.
+    // This simply converts from the `trap`, a `u8`, to the `Trap` enum.
     macro_rules! check {
-        ($($name:ident)*) => ($(if trap == TrapCode::$name as u8 {
-            return Some(TrapCode::$name);
+        ($($name:ident)*) => ($(if trap == Trap::$name as u8 {
+            return Some(Trap::$name);
         })*);
     }
 
     check! {
         StackOverflow
-        HeapOutOfBounds
+        MemoryOutOfBounds
         HeapMisaligned
         TableOutOfBounds
         IndirectCallToNull
@@ -184,6 +221,8 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<TrapCode> {
         UnreachableCodeReached
         Interrupt
         AlwaysTrapAdapter
+        OutOfFuel
+        AtomicWaitNonSharedMemory
     }
 
     if cfg!(debug_assertions) {
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 16fc731ae..a0628abf3 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -506,7 +506,7 @@ pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest)
     fuzz_config.set_spectest_compliant();
     log::debug!("running {:?}", test.file);
     let mut wast_context = WastContext::new(fuzz_config.to_store());
-    wast_context.register_spectest().unwrap();
+    wast_context.register_spectest(false).unwrap();
     wast_context
         .run_buffer(test.file, test.contents.as_bytes())
         .unwrap();
diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml
index 00a69d67e..93c23278b 100644
--- a/crates/runtime/Cargo.toml
+++ b/crates/runtime/Cargo.toml
@@ -45,6 +45,9 @@ features = [
   "Win32_Security",
 ]
 
+[dev-dependencies]
+once_cell = { workspace = true }
+
 [build-dependencies]
 cc = "1.0"
 
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index 5675a79cd..e8d9ed929 100644
--- a/crates/runtime/src/instance.rs
+++ b/crates/runtime/src/instance.rs
@@ -30,7 +30,7 @@ use wasmtime_environ::{
     packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex,
     DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex,
     GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex,
-    TableInitialization, TrapCode, VMOffsets, WasmType,
+    TableInitialization, Trap, VMOffsets, WasmType,
 };
 
 mod allocator;
@@ -196,6 +196,17 @@ impl Instance {
         }
     }
 
+    /// Get a locally defined or imported memory.
+    pub(crate) fn get_runtime_memory(&mut self, index: MemoryIndex) -> &mut Memory {
+        if let Some(defined_index) = self.module().defined_memory_index(index) {
+            unsafe { &mut *self.get_defined_memory(defined_index) }
+        } else {
+            let import = self.imported_memory(index);
+            let ctx = unsafe { &mut *import.vmctx };
+            unsafe { &mut *ctx.instance_mut().get_defined_memory(import.index) }
+        }
+    }
+
     /// Return the indexed `VMMemoryDefinition`.
     fn memory(&self, index: DefinedMemoryIndex) -> VMMemoryDefinition {
         unsafe { VMMemoryDefinition::load(self.memory_ptr(index)) }
@@ -580,7 +591,7 @@ impl Instance {
         dst: u32,
         src: u32,
         len: u32,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         // TODO: this `clone()` shouldn't be necessary but is used for now to
         // inform `rustc` that the lifetime of the elements here are
         // disconnected from the lifetime of `self`.
@@ -602,7 +613,7 @@ impl Instance {
         dst: u32,
         src: u32,
         len: u32,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init
 
         let table = unsafe { &mut *self.get_table(table_index) };
@@ -612,7 +623,7 @@ impl Instance {
             .and_then(|s| s.get(..usize::try_from(len).unwrap()))
         {
             Some(elements) => elements,
-            None => return Err(TrapCode::TableOutOfBounds),
+            None => return Err(Trap::TableOutOfBounds),
         };
 
         match table.element_type() {
@@ -662,7 +673,7 @@ impl Instance {
         src_index: MemoryIndex,
         src: u64,
         len: u64,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-memory-copy
 
         let src_mem = self.get_memory(src_index);
@@ -684,8 +695,8 @@ impl Instance {
         Ok(())
     }
 
-    fn validate_inbounds(&self, max: usize, ptr: u64, len: u64) -> Result<usize, TrapCode> {
-        let oob = || TrapCode::HeapOutOfBounds;
+    fn validate_inbounds(&self, max: usize, ptr: u64, len: u64) -> Result<usize, Trap> {
+        let oob = || Trap::MemoryOutOfBounds;
         let end = ptr
             .checked_add(len)
             .and_then(|i| usize::try_from(i).ok())
@@ -708,7 +719,7 @@ impl Instance {
         dst: u64,
         val: u8,
         len: u64,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         let memory = self.get_memory(memory_index);
         let dst = self.validate_inbounds(memory.current_length(), dst, len)?;
 
@@ -738,7 +749,7 @@ impl Instance {
         dst: u64,
         src: u32,
         len: u32,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         let range = match self.module().passive_data_map.get(&data_index).cloned() {
             Some(range) if !self.dropped_data.contains(data_index) => range,
             _ => 0..0,
@@ -757,7 +768,7 @@ impl Instance {
         dst: u64,
         src: u32,
         len: u32,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init
 
         let memory = self.get_memory(memory_index);
diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs
index ebf4f80b0..9f514b252 100644
--- a/crates/runtime/src/instance/allocator.rs
+++ b/crates/runtime/src/instance/allocator.rs
@@ -13,8 +13,8 @@ use std::sync::Arc;
 use thiserror::Error;
 use wasmtime_environ::{
     DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
-    MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, TrapCode,
-    VMOffsets, WasmType, WASM_PAGE_SIZE,
+    MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets,
+    WasmType, WASM_PAGE_SIZE,
 };
 
 #[cfg(feature = "pooling-allocator")]
@@ -105,7 +105,7 @@ pub enum InstantiationError {
 
     /// A trap ocurred during instantiation, after linking.
     #[error("Trap occurred during instantiation")]
-    Trap(TrapCode),
+    Trap(Trap),
 
     /// A limit on how many instances are supported has been reached.
     #[error("Limit of {0} concurrent instances has been reached")]
@@ -386,7 +386,7 @@ fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<(), I
         },
     );
     if !ok {
-        return Err(InstantiationError::Trap(TrapCode::HeapOutOfBounds));
+        return Err(InstantiationError::Trap(Trap::MemoryOutOfBounds));
     }
 
     Ok(())
diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs
index e89ebedcc..325e34740 100644
--- a/crates/runtime/src/lib.rs
+++ b/crates/runtime/src/lib.rs
@@ -40,6 +40,7 @@ mod instance;
 mod memory;
 mod mmap;
 mod mmap_vec;
+mod parking_spot;
 mod table;
 mod traphandlers;
 mod vmcontext;
diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs
index 6a7fb956e..384e7f190 100644
--- a/crates/runtime/src/libcalls.rs
+++ b/crates/runtime/src/libcalls.rs
@@ -55,15 +55,15 @@
 //! ```
 
 use crate::externref::VMExternRef;
-use crate::instance::Instance;
 use crate::table::{Table, TableElementType};
 use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
-use crate::TrapReason;
+use crate::{SharedMemory, TrapReason};
 use anyhow::Result;
 use std::mem;
 use std::ptr::{self, NonNull};
+use std::time::{Duration, Instant};
 use wasmtime_environ::{
-    DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TrapCode,
+    DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Trap,
 };
 
 /// Actually public trampolines which are used by the runtime as the entrypoint
@@ -228,7 +228,7 @@ unsafe fn table_fill(
     // `VMCallerCheckedAnyfunc` until we look at the table's element type.
     val: *mut u8,
     len: u32,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let instance = (*vmctx).instance_mut();
     let table_index = TableIndex::from_u32(table_index);
     let table = &mut *instance.get_table(table_index);
@@ -259,7 +259,7 @@ unsafe fn table_copy(
     dst: u32,
     src: u32,
     len: u32,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let dst_table_index = TableIndex::from_u32(dst_table_index);
     let src_table_index = TableIndex::from_u32(src_table_index);
     let instance = (*vmctx).instance_mut();
@@ -278,7 +278,7 @@ unsafe fn table_init(
     dst: u32,
     src: u32,
     len: u32,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let table_index = TableIndex::from_u32(table_index);
     let elem_index = ElemIndex::from_u32(elem_index);
     let instance = (*vmctx).instance_mut();
@@ -300,7 +300,7 @@ unsafe fn memory_copy(
     src_index: u32,
     src: u64,
     len: u64,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let src_index = MemoryIndex::from_u32(src_index);
     let dst_index = MemoryIndex::from_u32(dst_index);
     let instance = (*vmctx).instance_mut();
@@ -314,7 +314,7 @@ unsafe fn memory_fill(
     dst: u64,
     val: u32,
     len: u64,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let memory_index = MemoryIndex::from_u32(memory_index);
     let instance = (*vmctx).instance_mut();
     instance.memory_fill(memory_index, dst, val as u8, len)
@@ -328,7 +328,7 @@ unsafe fn memory_init(
     dst: u64,
     src: u32,
     len: u32,
-) -> Result<(), TrapCode> {
+) -> Result<(), Trap> {
     let memory_index = MemoryIndex::from_u32(memory_index);
     let data_index = DataIndex::from_u32(data_index);
     let instance = (*vmctx).instance_mut();
@@ -434,81 +434,81 @@ unsafe fn externref_global_set(vmctx: *mut VMContext, index: u32, externref: *mu
 unsafe fn memory_atomic_notify(
     vmctx: *mut VMContext,
     memory_index: u32,
-    addr: u64,
-    _count: u32,
+    addr_index: u64,
+    count: u32,
 ) -> Result<u32, TrapReason> {
     let memory = MemoryIndex::from_u32(memory_index);
-    let instance = (*vmctx).instance();
-    validate_atomic_addr(instance, memory, addr, 4, 4)?;
-    Err(
-        anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_notify) unsupported",)
-            .into(),
-    )
+    let instance = (*vmctx).instance_mut();
+    instance
+        .get_memory(memory)
+        .validate_addr(addr_index, 4, 4)?;
+
+    let shared_mem = instance.get_runtime_memory(memory).as_shared_memory();
+
+    if count == 0 {
+        return Ok(0);
+    }
+
+    let unparked_threads = shared_mem.map_or(0, |shared_mem| {
+        // SAFETY: checked `addr_index` above
+        unsafe { shared_mem.unchecked_atomic_notify(addr_index, count) }
+    });
+
+    Ok(unparked_threads)
 }
 
 // Implementation of `memory.atomic.wait32` for locally defined memories.
 unsafe fn memory_atomic_wait32(
     vmctx: *mut VMContext,
     memory_index: u32,
-    addr: u64,
-    _expected: u32,
-    _timeout: u64,
+    addr_index: u64,
+    expected: u32,
+    timeout: u64,
 ) -> Result<u32, TrapReason> {
+    // convert timeout to Instant, before any wait happens on locking
+    let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
+
     let memory = MemoryIndex::from_u32(memory_index);
-    let instance = (*vmctx).instance();
-    validate_atomic_addr(instance, memory, addr, 4, 4)?;
-    Err(
-        anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait32) unsupported",)
-            .into(),
-    )
+    let instance = (*vmctx).instance_mut();
+    let addr = instance
+        .get_memory(memory)
+        .validate_addr(addr_index, 4, 4)?;
+
+    let shared_mem: SharedMemory = instance
+        .get_runtime_memory(memory)
+        .as_shared_memory()
+        .ok_or(Trap::AtomicWaitNonSharedMemory)?;
+
+    // SAFETY: checked `addr_index` above
+    let res = unsafe { shared_mem.unchecked_atomic_wait32(addr_index, addr, expected, timeout) };
+    Ok(res)
 }
 
 // Implementation of `memory.atomic.wait64` for locally defined memories.
 unsafe fn memory_atomic_wait64(
     vmctx: *mut VMContext,
     memory_index: u32,
-    addr: u64,
-    _expected: u64,
-    _timeout: u64,
+    addr_index: u64,
+    expected: u64,
+    timeout: u64,
 ) -> Result<u32, TrapReason> {
-    let memory = MemoryIndex::from_u32(memory_index);
-    let instance = (*vmctx).instance();
-    validate_atomic_addr(instance, memory, addr, 8, 8)?;
-    Err(
-        anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait64) unsupported",)
-            .into(),
-    )
-}
+    // convert timeout to Instant, before any wait happens on locking
+    let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
 
-macro_rules! ensure {
-    ($cond:expr, $trap:expr) => {
-        if !($cond) {
-            return Err($trap);
-        }
-    };
-}
-
-/// In the configurations where bounds checks were elided in JIT code (because
-/// we are using static memories with virtual memory guard pages) this manual
-/// check is here so we don't segfault from Rust. For other configurations,
-/// these checks are required anyways.
-unsafe fn validate_atomic_addr(
-    instance: &Instance,
-    memory: MemoryIndex,
-    addr: u64,
-    access_size: u64,
-    access_alignment: u64,
-) -> Result<(), TrapCode> {
-    debug_assert!(access_alignment.is_power_of_two());
-    ensure!(addr % access_alignment == 0, TrapCode::HeapMisaligned);
-
-    let length = u64::try_from(instance.get_memory(memory).current_length()).unwrap();
-    ensure!(
-        addr.saturating_add(access_size) < length,
-        TrapCode::HeapOutOfBounds
-    );
-
-    Ok(())
+    let memory = MemoryIndex::from_u32(memory_index);
+    let instance = (*vmctx).instance_mut();
+    let addr = instance
+        .get_memory(memory)
+        .validate_addr(addr_index, 8, 8)?;
+
+    let shared_mem: SharedMemory = instance
+        .get_runtime_memory(memory)
+        .as_shared_memory()
+        .ok_or(Trap::AtomicWaitNonSharedMemory)?;
+
+    // SAFETY: checked `addr_index` above
+    let res = unsafe { shared_mem.unchecked_atomic_wait64(addr_index, addr, expected, timeout) };
+    Ok(res)
 }
 
 // Hook for when an instance runs out of fuel.
diff --git a/crates/runtime/src/memory.rs b/crates/runtime/src/memory.rs
index 8a6d30d43..5ba8bc52e 100644
--- a/crates/runtime/src/memory.rs
+++ b/crates/runtime/src/memory.rs
@@ -3,6 +3,7 @@
 //! `RuntimeLinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
 
 use crate::mmap::Mmap;
+use crate::parking_spot::{ParkResult, ParkingSpot};
 use crate::vmcontext::VMMemoryDefinition;
 use crate::MemoryImage;
 use crate::MemoryImageSlot;
@@ -10,9 +11,10 @@ use crate::Store;
 use anyhow::Error;
 use anyhow::{bail, format_err, Result};
 use std::convert::TryFrom;
-use std::sync::atomic::Ordering;
+use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
 use std::sync::{Arc, RwLock};
-use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM32_MAX_PAGES, WASM64_MAX_PAGES};
+use std::time::Instant;
+use wasmtime_environ::{MemoryPlan, MemoryStyle, Trap, WASM32_MAX_PAGES, WASM64_MAX_PAGES};
 
 const WASM_PAGE_SIZE: usize = wasmtime_environ::WASM_PAGE_SIZE as usize;
 const WASM_PAGE_SIZE_U64: u64 = wasmtime_environ::WASM_PAGE_SIZE as u64;
@@ -432,7 +434,8 @@ impl RuntimeLinearMemory for StaticMemory {
 /// [thread proposal]:
 ///     https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md#webassemblymemoryprototypegrow
 #[derive(Clone)]
-pub struct SharedMemory(Arc<RwLock<SharedMemoryInner>>);
+pub struct SharedMemory(Arc<SharedMemoryInner>);
+
 impl SharedMemory {
     /// Construct a new [`SharedMemory`].
     pub fn new(plan: MemoryPlan) -> Result<Self> {
@@ -458,16 +461,14 @@ impl SharedMemory {
             "cannot re-wrap a shared memory"
         );
         let def = LongTermVMMemoryDefinition(memory.vmmemory());
-        Ok(Self(Arc::new(RwLock::new(SharedMemoryInner {
-            memory: memory,
-            ty,
-            def,
-        }))))
+        let spot = ParkingSpot::default();
+        let inner = RwLock::new(SharedMemoryInnerRwLocked { memory, ty, def });
+        Ok(Self(Arc::new(SharedMemoryInner { spot, lock: inner })))
     }
 
     /// Return the memory type for this [`SharedMemory`].
     pub fn ty(&self) -> wasmtime_environ::Memory {
-        self.0.read().unwrap().ty
+        self.0.lock.read().unwrap().ty
     }
 
     /// Convert this shared memory into a [`Memory`].
@@ -477,21 +478,120 @@ impl SharedMemory {
 
     /// Return a mutable pointer to the shared memory's [VMMemoryDefinition].
     pub fn vmmemory_ptr_mut(&mut self) -> *mut VMMemoryDefinition {
-        &self.0.read().unwrap().def.0 as *const _ as *mut _
+        &self.0.lock.read().unwrap().def.0 as *const _ as *mut _
     }
 
     /// Return a pointer to the shared memory's [VMMemoryDefinition].
     pub fn vmmemory_ptr(&self) -> *const VMMemoryDefinition {
-        &self.0.read().unwrap().def.0 as *const _
+        &self.0.lock.read().unwrap().def.0 as *const _
+    }
+
+    /// Implementation of `memory.atomic.notify` for this shared memory.
+    pub fn atomic_notify(&self, addr_index: u64, count: u32) -> Result<u32, Trap> {
+        let definition = &self.0.lock.read().unwrap().def.0;
+        definition.validate_addr(addr_index, 4, 4)?;
+        // SAFETY: checked `addr_index` above
+        Ok(unsafe { self.unchecked_atomic_notify(addr_index, count) })
+    }
+
+    /// Unchecked implementation of `memory.atomic.notify` for this shared memory.
+    ///
+    /// # Safety
+    /// The caller must ensure that `addr_index` is a valid address in the memory.
+    pub unsafe fn unchecked_atomic_notify(&self, addr_index: u64, count: u32) -> u32 {
+        if count == 0 {
+            return 0;
+        }
+        self.0.spot.unpark(addr_index, count)
+    }
+
+    /// Implementation of `memory.atomic.wait32` for this shared memory.
+    pub fn atomic_wait32(
+        &self,
+        addr_index: u64,
+        expected: u32,
+        timeout: Option<Instant>,
+    ) -> Result<u32, Trap> {
+        let definition = &self.0.lock.read().unwrap().def.0;
+        let addr = definition.validate_addr(addr_index, 4, 4)?;
+        // SAFETY: checked `addr` above
+        Ok(unsafe { self.unchecked_atomic_wait32(addr_index, addr, expected, timeout) })
+    }
+
+    /// Unchecked implementation of `memory.atomic.wait32` for this shared memory.
+    ///
+    /// # Safety
+    /// The caller must ensure that `addr` is a valid address in the memory.
+    pub unsafe fn unchecked_atomic_wait32(
+        &self,
+        addr_index: u64,
+        addr: *const u8,
+        expected: u32,
+        timeout: Option<Instant>,
+    ) -> u32 {
+        // SAFETY: `addr_index` was validated by `validate_addr` above.
+        let atomic = unsafe { &*(addr as *const AtomicU32) };
+
+        // We want the sequential consistency of `SeqCst` to ensure that the `load` sees the value that the `notify` will/would see.
+        // All WASM atomic operations are also `SeqCst`.
+        let validate = || atomic.load(Ordering::SeqCst) == expected;
+
+        match self.0.spot.park(addr_index, validate, timeout) {
+            ParkResult::Unparked => 0,
+            ParkResult::Invalid => 1,
+            ParkResult::TimedOut => 2,
+        }
+    }
+
+    /// Implementation of `memory.atomic.wait64` for this shared memory.
+    pub fn atomic_wait64(
+        &self,
+        addr_index: u64,
+        expected: u64,
+        timeout: Option<Instant>,
+    ) -> Result<u32, Trap> {
+        let definition = &self.0.lock.read().unwrap().def.0;
+        let addr = definition.validate_addr(addr_index, 8, 8)?;
+        // SAFETY: checked `addr` above
+        Ok(unsafe { self.unchecked_atomic_wait64(addr_index, addr, expected, timeout) })
+    }
+    /// Unchecked implementation of `memory.atomic.wait64` for this shared memory.
+    ///
+    /// # Safety
+    /// The caller must ensure that `addr` is a valid address in the memory.
+    pub unsafe fn unchecked_atomic_wait64(
+        &self,
+        addr_index: u64,
+        addr: *const u8,
+        expected: u64,
+        timeout: Option<Instant>,
+    ) -> u32 {
+        // SAFETY: `addr_index` was validated by `validate_addr` above.
+        let atomic = unsafe { &*(addr as *const AtomicU64) };
+
+        // We want the sequential consistency of `SeqCst` to ensure that the `load` sees the value that the `notify` will/would see.
+        // All WASM atomic operations are also `SeqCst`.
+        let validate = || atomic.load(Ordering::SeqCst) == expected;
+
+        match self.0.spot.park(addr_index, validate, timeout) {
+            ParkResult::Unparked => 0,
+            ParkResult::Invalid => 1,
+            ParkResult::TimedOut => 2,
+        }
     }
 }
 
-struct SharedMemoryInner {
+struct SharedMemoryInnerRwLocked {
     memory: Box<dyn RuntimeLinearMemory>,
     ty: wasmtime_environ::Memory,
     def: LongTermVMMemoryDefinition,
 }
 
+struct SharedMemoryInner {
+    lock: RwLock<SharedMemoryInnerRwLocked>,
+    spot: ParkingSpot,
+}
+
 /// Shared memory needs some representation of a `VMMemoryDefinition` for
 /// JIT-generated code to access. This structure owns the base pointer and
 /// length to the actual memory and we share this definition across threads by:
@@ -507,11 +607,11 @@ unsafe impl Sync for LongTermVMMemoryDefinition {}
 /// Proxy all calls through the [`RwLock`].
 impl RuntimeLinearMemory for SharedMemory {
     fn byte_size(&self) -> usize {
-        self.0.read().unwrap().memory.byte_size()
+        self.0.lock.read().unwrap().memory.byte_size()
     }
 
     fn maximum_byte_size(&self) -> Option<usize> {
-        self.0.read().unwrap().memory.maximum_byte_size()
+        self.0.lock.read().unwrap().memory.maximum_byte_size()
     }
 
     fn grow(
@@ -519,7 +619,7 @@ impl RuntimeLinearMemory for SharedMemory {
         delta_pages: u64,
         store: Option<&mut dyn Store>,
     ) -> Result<Option<(usize, usize)>, Error> {
-        let mut inner = self.0.write().unwrap();
+        let mut inner = self.0.lock.write().unwrap();
         let result = inner.memory.grow(delta_pages, store)?;
         if let Some((_old_size_in_bytes, new_size_in_bytes)) = result {
             // Store the new size to the `VMMemoryDefinition` for JIT-generated
@@ -551,7 +651,7 @@ impl RuntimeLinearMemory for SharedMemory {
     }
 
     fn grow_to(&mut self, size: usize) -> Result<()> {
-        self.0.write().unwrap().memory.grow_to(size)
+        self.0.lock.write().unwrap().memory.grow_to(size)
     }
 
     fn vmmemory(&mut self) -> VMMemoryDefinition {
@@ -563,7 +663,7 @@ impl RuntimeLinearMemory for SharedMemory {
     }
 
     fn needs_init(&self) -> bool {
-        self.0.read().unwrap().memory.needs_init()
+        self.0.lock.read().unwrap().memory.needs_init()
     }
 
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
diff --git a/crates/runtime/src/parking_spot.rs b/crates/runtime/src/parking_spot.rs
new file mode 100644
index 000000000..a958d6d75
--- /dev/null
+++ b/crates/runtime/src/parking_spot.rs
@@ -0,0 +1,445 @@
+//! Implements thread wait and notify primitives with `std::sync` primitives.
+//!
+//! This is a simplified version of the `parking_lot_core` crate.
+//!
+//! There are two main operations that can be performed:
+//!
+//! - *Parking* refers to suspending the thread while simultaneously enqueuing it
+//! on a queue keyed by some address.
+//! - *Unparking* refers to dequeuing a thread from a queue keyed by some address
+//! and resuming it.
+
+#![deny(clippy::all)]
+#![deny(clippy::pedantic)]
+#![deny(missing_docs)]
+#![deny(unsafe_code)]
+
+use std::collections::BTreeMap;
+use std::sync::{Arc, Condvar, Mutex};
+use std::time::Instant;
+
+/// Result of a park operation.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum ParkResult {
+    /// Unparked by another thread.
+    Unparked,
+
+    /// The validation callback returned false.
+    Invalid,
+
+    /// The timeout expired.
+    TimedOut,
+}
+
+#[derive(Default, Debug)]
+struct Spot {
+    /// The number of threads parked on this spot.
+    num_parked: u32,
+
+    /// The number of threads that have been unparked but not yet woken up.
+    /// This is used to avoid spurious wakeups.
+    to_unpark: u32,
+
+    /// The [`Condvar`] used to notify parked threads.
+    cvar: Arc<Condvar>,
+}
+
+/// The thread global `ParkingSpot`.
+#[derive(Default, Debug)]
+pub struct ParkingSpot {
+    inner: Mutex<BTreeMap<u64, Spot>>,
+}
+
+impl ParkingSpot {
+    /// Park the current thread until it is unparked or a timeout is reached.
+    ///
+    /// The `key` is used to identify the parking spot. If another thread calls
+    /// `unpark_all` or `unpark` with the same key, the current thread will be unparked.
+    ///
+    /// The `validate` callback is called before parking.
+    /// If it returns `false`, the thread is not parked and `ParkResult::Invalid` is returned.
+    ///
+    /// The `timeout` argument specifies the maximum amount of time the thread will be parked.
+    pub fn park(
+        &self,
+        key: u64,
+        validate: impl FnOnce() -> bool,
+        timeout: impl Into<Option<Instant>>,
+    ) -> ParkResult {
+        self.park_inner(key, validate, timeout.into())
+    }
+
+    fn park_inner(
+        &self,
+        key: u64,
+        validate: impl FnOnce() -> bool,
+        timeout: Option<Instant>,
+    ) -> ParkResult {
+        let mut inner = self
+            .inner
+            .lock()
+            .expect("failed to lock inner parking table");
+
+        // check validation with lock held
+        if !validate() {
+            return ParkResult::Invalid;
+        }
+
+        // clone the condvar, so we can move the lock
+        let cvar = {
+            let spot = inner.entry(key).or_insert_with(Spot::default);
+            spot.num_parked = spot
+                .num_parked
+                .checked_add(1)
+                .expect("parking spot number overflow");
+            spot.cvar.clone()
+        };
+
+        loop {
+            let timed_out = if let Some(timeout) = timeout {
+                let now = Instant::now();
+                if now >= timeout {
+                    true
+                } else {
+                    let dur = timeout - now;
+                    let (lock, result) = cvar
+                        .wait_timeout(inner, dur)
+                        .expect("failed to wait for condition");
+                    inner = lock;
+                    result.timed_out()
+                }
+            } else {
+                inner = cvar.wait(inner).expect("failed to wait for condition");
+                false
+            };
+
+            let spot = inner.get_mut(&key).expect("failed to get spot");
+
+            if !timed_out {
+                if spot.to_unpark == 0 {
+                    continue;
+                }
+
+                spot.to_unpark -= 1;
+            }
+
+            spot.num_parked = spot
+                .num_parked
+                .checked_sub(1)
+                .expect("corrupted parking spot state");
+
+            if spot.num_parked == 0 {
+                assert_eq!(spot.to_unpark, 0);
+                inner
+                    .remove(&key)
+                    .expect("failed to remove spot from inner parking table");
+            }
+
+            if timed_out {
+                return ParkResult::TimedOut;
+            }
+
+            return ParkResult::Unparked;
+        }
+    }
+
+    /// Unpark at most `n` threads that are parked with the given key.
+    ///
+    /// Returns the number of threads that were actually unparked.
+    pub fn unpark(&self, key: u64, n: u32) -> u32 {
+        let mut num_unpark = 0;
+
+        self.with_lot(key, |spot| {
+            num_unpark = n.min(spot.num_parked - spot.to_unpark);
+            spot.to_unpark += num_unpark;
+            if n >= num_unpark {
+                spot.cvar.notify_all();
+            } else {
+                for _ in 0..num_unpark {
+                    spot.cvar.notify_one();
+                }
+            }
+        });
+
+        num_unpark
+    }
+
+    fn with_lot<F: FnMut(&mut Spot)>(&self, key: u64, mut f: F) {
+        let mut inner = self
+            .inner
+            .lock()
+            .expect("failed to lock inner parking table");
+        if let Some(spot) = inner.get_mut(&key) {
+            f(spot);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ParkingSpot;
+    use once_cell::sync::Lazy;
+    use std::ptr::addr_of;
+    use std::sync::atomic::{AtomicU64, Ordering};
+    use std::thread;
+
+    static PARKING_SPOT: Lazy<ParkingSpot> = Lazy::new(ParkingSpot::default);
+
+    static ATOMIC: AtomicU64 = AtomicU64::new(0);
+
+    #[test]
+    fn atomic_wait_notify() {
+        let thread1 = thread::spawn(|| {
+            let atomic_key = addr_of!(ATOMIC) as u64;
+            ATOMIC.store(1, Ordering::SeqCst);
+            PARKING_SPOT.unpark(atomic_key, u32::MAX);
+            PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) == 1, None);
+        });
+
+        let thread2 = thread::spawn(|| {
+            let atomic_key = addr_of!(ATOMIC) as u64;
+            while ATOMIC.load(Ordering::SeqCst) != 1 {
+                PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) != 1, None);
+            }
+            ATOMIC.store(2, Ordering::SeqCst);
+            PARKING_SPOT.unpark(atomic_key, u32::MAX);
+            PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) == 2, None);
+        });
+
+        let thread3 = thread::spawn(|| {
+            let atomic_key = addr_of!(ATOMIC) as u64;
+            while ATOMIC.load(Ordering::SeqCst) != 2 {
+                PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) != 2, None);
+            }
+            ATOMIC.store(3, Ordering::SeqCst);
+            PARKING_SPOT.unpark(atomic_key, u32::MAX);
+
+            PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) == 3, None);
+        });
+
+        let atomic_key = addr_of!(ATOMIC) as u64;
+        while ATOMIC.load(Ordering::SeqCst) != 3 {
+            PARKING_SPOT.park(atomic_key, || ATOMIC.load(Ordering::SeqCst) != 3, None);
+        }
+        ATOMIC.store(4, Ordering::SeqCst);
+        PARKING_SPOT.unpark(atomic_key, u32::MAX);
+
+        thread1.join().unwrap();
+        thread2.join().unwrap();
+        thread3.join().unwrap();
+    }
+
+    mod parking_lot {
+        // This is a modified version of the parking_lot_core tests,
+        // which are licensed under the MIT and Apache 2.0 licenses.
+        use super::*;
+        use std::sync::atomic::{AtomicIsize, AtomicU32};
+        use std::sync::Arc;
+        use std::time::Duration;
+
+        macro_rules! test {
+            ( $( $name:ident(
+                repeats: $repeats:expr,
+                latches: $latches:expr,
+                delay: $delay:expr,
+                threads: $threads:expr,
+                single_unparks: $single_unparks:expr);
+            )* ) => {
+                $(
+                #[test]
+                fn $name() {
+                    if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
+                        return;
+                    }
+                    let delay = Duration::from_micros($delay);
+                    for _ in 0..$repeats {
+                        run_parking_test($latches, delay, $threads, $single_unparks);
+                    }
+                })*
+            };
+        }
+
+        test! {
+            unpark_all_one_fast(
+                repeats: 10000, latches: 1, delay: 0, threads: 1, single_unparks: 0
+            );
+            unpark_all_hundred_fast(
+                repeats: 100, latches: 1, delay: 0, threads: 100, single_unparks: 0
+            );
+            unpark_one_one_fast(
+                repeats: 1000, latches: 1, delay: 0, threads: 1, single_unparks: 1
+            );
+            unpark_one_hundred_fast(
+                repeats: 20, latches: 1, delay: 0, threads: 100, single_unparks: 100
+            );
+            unpark_one_fifty_then_fifty_all_fast(
+                repeats: 50, latches: 1, delay: 0, threads: 100, single_unparks: 50
+            );
+            unpark_all_one(
+                repeats: 100, latches: 1, delay: 10000, threads: 1, single_unparks: 0
+            );
+            unpark_all_hundred(
+                repeats: 100, latches: 1, delay: 10000, threads: 100, single_unparks: 0
+            );
+            unpark_one_one(
+                repeats: 10, latches: 1, delay: 10000, threads: 1, single_unparks: 1
+            );
+            unpark_one_fifty(
+                repeats: 1, latches: 1, delay: 10000, threads: 50, single_unparks: 50
+            );
+            unpark_one_fifty_then_fifty_all(
+                repeats: 2, latches: 1, delay: 10000, threads: 100, single_unparks: 50
+            );
+            hundred_unpark_all_one_fast(
+                repeats: 100, latches: 100, delay: 0, threads: 1, single_unparks: 0
+            );
+            hundred_unpark_all_one(
+                repeats: 1, latches: 100, delay: 10000, threads: 1, single_unparks: 0
+            );
+        }
+
+        fn run_parking_test(
+            num_latches: usize,
+            delay: Duration,
+            num_threads: u32,
+            num_single_unparks: u32,
+        ) {
+            let mut tests = Vec::with_capacity(num_latches);
+
+            for _ in 0..num_latches {
+                let test = Arc::new(SingleLatchTest::new(num_threads));
+                let mut threads = Vec::with_capacity(num_threads as _);
+                for _ in 0..num_threads {
+                    let test = test.clone();
+                    threads.push(thread::spawn(move || test.run()));
+                }
+                tests.push((test, threads));
+            }
+
+            for unpark_index in 0..num_single_unparks {
+                thread::sleep(delay);
+                for (test, _) in &tests {
+                    test.unpark_one(unpark_index);
+                }
+            }
+
+            for (test, threads) in tests {
+                test.finish(num_single_unparks);
+                for thread in threads {
+                    thread.join().expect("Test thread panic");
+                }
+            }
+        }
+
+        struct SingleLatchTest {
+            semaphore: AtomicIsize,
+            num_awake: AtomicU32,
+            /// Total number of threads participating in this test.
+            num_threads: u32,
+        }
+
+        impl SingleLatchTest {
+            pub fn new(num_threads: u32) -> Self {
+                Self {
+                    // This implements a fair (FIFO) semaphore, and it starts out unavailable.
+                    semaphore: AtomicIsize::new(0),
+                    num_awake: AtomicU32::new(0),
+                    num_threads,
+                }
+            }
+
+            pub fn run(&self) {
+                // Get one slot from the semaphore
+                self.down();
+
+                self.num_awake.fetch_add(1, Ordering::SeqCst);
+            }
+
+            pub fn unpark_one(&self, _single_unpark_index: u32) {
+                let num_awake_before_up = self.num_awake.load(Ordering::SeqCst);
+
+                self.up();
+
+                // Wait for a parked thread to wake up and update num_awake + last_awoken.
+                while self.num_awake.load(Ordering::SeqCst) != num_awake_before_up + 1 {
+                    thread::yield_now();
+                }
+            }
+
+            pub fn finish(&self, num_single_unparks: u32) {
+                // The amount of threads not unparked via unpark_one
+                let mut num_threads_left =
+                    self.num_threads.checked_sub(num_single_unparks).unwrap();
+
+                // Wake remaining threads up with unpark_all. Has to be in a loop, because there might
+                // still be threads that has not yet parked.
+                while num_threads_left > 0 {
+                    let mut num_waiting_on_address = 0;
+                    PARKING_SPOT.with_lot(self.semaphore_addr(), |thread_data| {
+                        num_waiting_on_address = thread_data.num_parked;
+                    });
+                    assert!(num_waiting_on_address <= num_threads_left);
+
+                    let num_awake_before_unpark = self.num_awake.load(Ordering::SeqCst);
+
+                    let num_unparked = PARKING_SPOT.unpark(self.semaphore_addr(), u32::MAX);
+                    assert!(num_unparked >= num_waiting_on_address);
+                    assert!(num_unparked <= num_threads_left);
+
+                    // Wait for all unparked threads to wake up and update num_awake + last_awoken.
+                    while self.num_awake.load(Ordering::SeqCst)
+                        != num_awake_before_unpark + num_unparked
+                    {
+                        thread::yield_now();
+                    }
+
+                    num_threads_left = num_threads_left.checked_sub(num_unparked).unwrap();
+                }
+                // By now, all threads should have been woken up
+                assert_eq!(self.num_awake.load(Ordering::SeqCst), self.num_threads);
+
+                // Make sure no thread is parked on our semaphore address
+                let mut num_waiting_on_address = 0;
+                PARKING_SPOT.with_lot(self.semaphore_addr(), |thread_data| {
+                    num_waiting_on_address = thread_data.num_parked;
+                });
+                assert_eq!(num_waiting_on_address, 0);
+            }
+
+            pub fn down(&self) {
+                let old_semaphore_value = self.semaphore.fetch_sub(1, Ordering::SeqCst);
+
+                if old_semaphore_value > 0 {
+                    // We acquired the semaphore. Done.
+                    return;
+                }
+
+                // We need to wait.
+                let validate = || true;
+                PARKING_SPOT.park(self.semaphore_addr(), validate, None);
+            }
+
+            pub fn up(&self) {
+                let old_semaphore_value = self.semaphore.fetch_add(1, Ordering::SeqCst);
+
+                // Check if anyone was waiting on the semaphore. If they were, then pass ownership to them.
+                if old_semaphore_value < 0 {
+                    // We need to continue until we have actually unparked someone. It might be that
+                    // the thread we want to pass ownership to has decremented the semaphore counter,
+                    // but not yet parked.
+                    loop {
+                        match PARKING_SPOT.unpark(self.semaphore_addr(), 1) {
+                            1 => break,
+                            0 => (),
+                            i => panic!("Should not wake up {i} threads"),
+                        }
+                    }
+                }
+            }
+
+            fn semaphore_addr(&self) -> u64 {
+                addr_of!(self.semaphore) as _
+            }
+        }
+    }
+}
diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs
index 70bea7014..ce7389886 100644
--- a/crates/runtime/src/table.rs
+++ b/crates/runtime/src/table.rs
@@ -8,7 +8,7 @@ use anyhow::{bail, format_err, Error, Result};
 use std::convert::{TryFrom, TryInto};
 use std::ops::Range;
 use std::ptr;
-use wasmtime_environ::{TablePlan, TrapCode, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK};
+use wasmtime_environ::{TablePlan, Trap, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK};
 
 /// An element going into or coming out of a table.
 ///
@@ -267,7 +267,7 @@ impl Table {
         &mut self,
         dst: u32,
         items: impl ExactSizeIterator<Item = *mut VMCallerCheckedAnyfunc>,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         assert!(self.element_type() == TableElementType::Func);
 
         let elements = match self
@@ -276,7 +276,7 @@ impl Table {
             .and_then(|s| s.get_mut(..items.len()))
         {
             Some(elements) => elements,
-            None => return Err(TrapCode::TableOutOfBounds),
+            None => return Err(Trap::TableOutOfBounds),
         };
 
         for (item, slot) in items.zip(elements) {
@@ -290,14 +290,14 @@ impl Table {
     /// Fill `table[dst..dst + len]` with `val`.
     ///
     /// Returns a trap error on out-of-bounds accesses.
-    pub fn fill(&mut self, dst: u32, val: TableElement, len: u32) -> Result<(), TrapCode> {
+    pub fn fill(&mut self, dst: u32, val: TableElement, len: u32) -> Result<(), Trap> {
         let start = dst as usize;
         let end = start
             .checked_add(len as usize)
-            .ok_or_else(|| TrapCode::TableOutOfBounds)?;
+            .ok_or_else(|| Trap::TableOutOfBounds)?;
 
         if end > self.size() as usize {
-            return Err(TrapCode::TableOutOfBounds);
+            return Err(Trap::TableOutOfBounds);
         }
 
         debug_assert!(self.type_matches(&val));
@@ -412,7 +412,7 @@ impl Table {
         dst_index: u32,
         src_index: u32,
         len: u32,
-    ) -> Result<(), TrapCode> {
+    ) -> Result<(), Trap> {
         // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
 
         if src_index
@@ -422,7 +422,7 @@ impl Table {
                 .checked_add(len)
                 .map_or(true, |m| m > (*dst_table).size())
         {
-            return Err(TrapCode::TableOutOfBounds);
+            return Err(Trap::TableOutOfBounds);
         }
 
         debug_assert!(
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index f9772df2a..215bfddc0 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -10,7 +10,6 @@ use std::cell::{Cell, UnsafeCell};
 use std::mem::MaybeUninit;
 use std::ptr;
 use std::sync::Once;
-use wasmtime_environ::TrapCode;
 
 pub use self::backtrace::Backtrace;
 pub use self::tls::{tls_eager_initialize, TlsRestore};
@@ -112,7 +111,7 @@ pub unsafe fn raise_user_trap(error: Error, needs_backtrace: bool) -> ! {
 /// Only safe to call when wasm code is on the stack, aka `catch_traps` must
 /// have been previously called. Additionally no Rust destructors can be on the
 /// stack. They will be skipped and not executed.
-pub unsafe fn raise_lib_trap(trap: TrapCode) -> ! {
+pub unsafe fn raise_lib_trap(trap: wasmtime_environ::Trap) -> ! {
     raise_trap(TrapReason::Wasm(trap))
 }
 
@@ -153,7 +152,7 @@ pub enum TrapReason {
     Jit(usize),
 
     /// A trap raised from a wasm libcall
-    Wasm(TrapCode),
+    Wasm(wasmtime_environ::Trap),
 }
 
 impl TrapReason {
@@ -185,8 +184,8 @@ impl From<Error> for TrapReason {
     }
 }
 
-impl From<TrapCode> for TrapReason {
-    fn from(code: TrapCode) -> Self {
+impl From<wasmtime_environ::Trap> for TrapReason {
+    fn from(code: wasmtime_environ::Trap) -> Self {
         TrapReason::Wasm(code)
     }
 }
diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs
index ae15342ea..f954d70e0 100644
--- a/crates/runtime/src/vmcontext.rs
+++ b/crates/runtime/src/vmcontext.rs
@@ -12,7 +12,7 @@ use std::ptr::NonNull;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::u32;
 pub use vm_host_func_context::VMHostFuncContext;
-use wasmtime_environ::DefinedMemoryIndex;
+use wasmtime_environ::{DefinedMemoryIndex, Trap};
 
 pub const VMCONTEXT_MAGIC: u32 = u32::from_le_bytes(*b"core");
 
@@ -248,6 +248,30 @@ impl VMMemoryDefinition {
             current_length: other.current_length().into(),
         }
     }
+
+    /// In the configurations where bounds checks were elided in JIT code (because
+    /// we are using static memories with virtual memory guard pages) this manual
+    /// check is here so we don't segfault from Rust. For other configurations,
+    /// these checks are required anyways.
+    pub fn validate_addr(
+        &self,
+        addr: u64,
+        access_size: u64,
+        access_alignment: u64,
+    ) -> Result<*const u8, Trap> {
+        debug_assert!(access_alignment.is_power_of_two());
+        if !(addr % access_alignment == 0) {
+            return Err(Trap::HeapMisaligned);
+        }
+
+        let length = u64::try_from(self.current_length()).unwrap();
+        if !(addr.saturating_add(access_size) < length) {
+            return Err(Trap::MemoryOutOfBounds);
+        }
+
+        // SAFETY: checked above that the address is in bounds
+        Ok(unsafe { self.base.add(addr as usize) })
+    }
 }
 
 #[cfg(test)]
diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs
index 023acba47..a30e01bbd 100644
--- a/crates/wasmtime/src/component/func/host.rs
+++ b/crates/wasmtime/src/component/func/host.rs
@@ -1,7 +1,7 @@
 use crate::component::func::{Memory, MemoryMut, Options};
 use crate::component::storage::slice_to_storage_mut;
 use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Type, Val};
-use crate::{AsContextMut, StoreContextMut, Trap, ValRaw};
+use crate::{AsContextMut, StoreContextMut, ValRaw};
 use anyhow::{anyhow, bail, Context, Result};
 use std::any::Any;
 use std::mem::{self, MaybeUninit};
@@ -270,7 +270,7 @@ fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<us
 unsafe fn handle_result(func: impl FnOnce() -> Result<()>) {
     match panic::catch_unwind(AssertUnwindSafe(func)) {
         Ok(Ok(())) => {}
-        Ok(Err(e)) => Trap::raise(e),
+        Ok(Err(e)) => crate::trap::raise(e),
         Err(e) => wasmtime_runtime::resume_panic(e),
     }
 }
diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs
index ae38e3301..77b3ff133 100644
--- a/crates/wasmtime/src/component/matching.rs
+++ b/crates/wasmtime/src/component/matching.rs
@@ -71,7 +71,7 @@ impl TypeChecker<'_> {
         // Like modules, every export in the expected type must be present in
         // the actual type. It's ok, though, to have extra exports in the actual
         // type.
-        for (name, expected) in expected.exports.iter() {
+        for (name, (_url, expected)) in expected.exports.iter() {
             // Interface types may be exported from a component in order to give them a name, but
             // they don't have a definition in the sense that this search is interested in, so
             // ignore them.
diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs
index 177edc18a..50eb51139 100644
--- a/crates/wasmtime/src/externals.rs
+++ b/crates/wasmtime/src/externals.rs
@@ -2,7 +2,7 @@ use crate::store::{StoreData, StoreOpaque, Stored};
 use crate::trampoline::{generate_global_export, generate_table_export};
 use crate::{
     AsContext, AsContextMut, Engine, ExternRef, ExternType, Func, GlobalType, Memory, Mutability,
-    SharedMemory, TableType, Trap, Val, ValType,
+    SharedMemory, TableType, Val, ValType,
 };
 use anyhow::{anyhow, bail, Result};
 use std::mem;
@@ -462,9 +462,7 @@ impl Table {
         let init = init.into_table_element(store, ty.element())?;
         unsafe {
             let table = Table::from_wasmtime_table(wasmtime_export, store);
-            (*table.wasmtime_table(store, std::iter::empty()))
-                .fill(0, init, ty.minimum())
-                .map_err(|c| Trap::from_env(c))?;
+            (*table.wasmtime_table(store, std::iter::empty())).fill(0, init, ty.minimum())?;
 
             Ok(table)
         }
@@ -652,8 +650,7 @@ impl Table {
         let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX));
         let src_table = src_table.wasmtime_table(store, src_range);
         unsafe {
-            runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
-                .map_err(|c| Trap::from_env(c))?;
+            runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)?;
         }
         Ok(())
     }
@@ -681,9 +678,7 @@ impl Table {
 
         let table = self.wasmtime_table(store, std::iter::empty());
         unsafe {
-            (*table)
-                .fill(dst, val, len)
-                .map_err(|c| Trap::from_env(c))?;
+            (*table).fill(dst, val, len)?;
         }
 
         Ok(())
diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs
index ecc3f22b4..43cc546e3 100644
--- a/crates/wasmtime/src/func.rs
+++ b/crates/wasmtime/src/func.rs
@@ -1,7 +1,7 @@
 use crate::store::{StoreData, StoreOpaque, Stored};
 use crate::{
     AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, StoreContext,
-    StoreContextMut, Trap, Val, ValRaw, ValType,
+    StoreContextMut, Val, ValRaw, ValType,
 };
 use anyhow::{bail, Context as _, Error, Result};
 use std::future::Future;
@@ -343,6 +343,8 @@ impl Func {
     ///
     /// For more information about errors in Wasmtime see the [`Trap`]
     /// documentation.
+    ///
+    /// [`Trap`]: crate::Trap
     #[cfg(compiler)]
     #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
     pub fn new<T>(
@@ -581,6 +583,8 @@ impl Func {
     /// For more information about errors in Wasmtime see the [`Trap`]
     /// documentation.
     ///
+    /// [`Trap`]: crate::Trap
+    ///
     /// # Examples
     ///
     /// First up we can see how simple wasm imports can be implemented, such
@@ -816,6 +820,8 @@ impl Func {
     /// Errors typically indicate that execution of WebAssembly was halted
     /// mid-way and did not complete after the error condition happened.
     ///
+    /// [`Trap`]: crate::Trap
+    ///
     /// # Panics
     ///
     /// This function will panic if called on a function belonging to an async
@@ -1305,7 +1311,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
         );
         exit_wasm(store, exit);
         store.0.call_hook(CallHook::ReturningFromWasm)?;
-        result.map_err(|t| Trap::from_runtime_box(store.0, t))
+        result.map_err(|t| crate::trap::from_runtime_box(store.0, t))
     }
 }
 
@@ -1947,7 +1953,7 @@ macro_rules! impl_into_func {
 
                     match result {
                         CallResult::Ok(val) => val,
-                        CallResult::Trap(err) => Trap::raise(err),
+                        CallResult::Trap(err) => crate::trap::raise(err),
                         CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic),
                     }
                 }
diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs
index 970fb030a..a0c5869cc 100644
--- a/crates/wasmtime/src/instance.rs
+++ b/crates/wasmtime/src/instance.rs
@@ -3,7 +3,7 @@ use crate::store::{InstanceId, StoreOpaque, Stored};
 use crate::types::matching;
 use crate::{
     AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, SharedMemory,
-    StoreContextMut, Table, Trap, TypedFunc,
+    StoreContextMut, Table, TypedFunc,
 };
 use anyhow::{anyhow, bail, Context, Error, Result};
 use std::mem;
@@ -91,6 +91,8 @@ impl Instance {
     /// check for trap errors, you can use `error.downcast::<Trap>()`. For more
     /// about error handling see the [`Trap`] documentation.
     ///
+    /// [`Trap`]: crate::Trap
+    ///
     /// # Panics
     ///
     /// This function will panic if called with a store associated with a
@@ -325,7 +327,7 @@ impl Instance {
             )
             .map_err(|e| -> Error {
                 match e {
-                    InstantiationError::Trap(trap) => Trap::from_env(trap).into(),
+                    InstantiationError::Trap(trap) => trap.into(),
                     other => other.into(),
                 }
             })?;
diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs
index d57ad218c..21bae8c5a 100644
--- a/crates/wasmtime/src/module/registry.rs
+++ b/crates/wasmtime/src/module/registry.rs
@@ -3,14 +3,13 @@
 use crate::code::CodeObject;
 #[cfg(feature = "component-model")]
 use crate::component::Component;
-use crate::{FrameInfo, Module};
+use crate::{FrameInfo, Module, Trap};
 use once_cell::sync::Lazy;
 use std::collections::btree_map::Entry;
 use std::{
     collections::BTreeMap,
     sync::{Arc, RwLock},
 };
-use wasmtime_environ::TrapCode;
 use wasmtime_jit::CodeMemory;
 use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
 
@@ -132,7 +131,7 @@ impl ModuleRegistry {
     }
 
     /// Fetches trap information about a program counter in a backtrace.
-    pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
+    pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
         let (code, offset) = self.code(pc)?;
         wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
     }
diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs
index 8799cf85e..587990759 100644
--- a/crates/wasmtime/src/trampoline/func.rs
+++ b/crates/wasmtime/src/trampoline/func.rs
@@ -1,6 +1,6 @@
 //! Support for a calling of an imported function.
 
-use crate::{Engine, FuncType, Trap, ValRaw};
+use crate::{Engine, FuncType, ValRaw};
 use anyhow::Result;
 use std::panic::{self, AssertUnwindSafe};
 use std::ptr::NonNull;
@@ -56,7 +56,7 @@ unsafe extern "C" fn stub_fn<F>(
         // call-site, which gets unwrapped in `Trap::from_runtime` later on as we
         // convert from the internal `Trap` type to our own `Trap` type in this
         // crate.
-        Ok(Err(trap)) => Trap::raise(trap.into()),
+        Ok(Err(trap)) => crate::trap::raise(trap.into()),
 
         // And finally if the imported function panicked, then we trigger the
         // form of unwinding that's safe to jump over wasm code on all
diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs
index 7b2631e9b..8c1d21aa4 100644
--- a/crates/wasmtime/src/trap.rs
+++ b/crates/wasmtime/src/trap.rs
@@ -2,7 +2,7 @@ use crate::store::StoreOpaque;
 use crate::Module;
 use anyhow::Error;
 use std::fmt;
-use wasmtime_environ::{EntityRef, FilePos, TrapCode};
+use wasmtime_environ::{EntityRef, FilePos};
 use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
 
 /// Representation of a WebAssembly trap and what caused it to occur.
@@ -65,168 +65,64 @@ use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
 /// # Ok(())
 /// # }
 /// ```
-//
-// The code can be accessed from the c-api, where the possible values are translated
-// into enum values defined there:
-//
-// * `wasm_trap_code` in c-api/src/trap.rs, and
-// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
-//
-// These need to be kept in sync.
-#[non_exhaustive]
-#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
-pub enum Trap {
-    /// The current stack space was exhausted.
-    StackOverflow,
-
-    /// An out-of-bounds memory access.
-    MemoryOutOfBounds,
-
-    /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
-    HeapMisaligned,
-
-    /// An out-of-bounds access to a table.
-    TableOutOfBounds,
-
-    /// Indirect call to a null table entry.
-    IndirectCallToNull,
-
-    /// Signature mismatch on indirect call.
-    BadSignature,
-
-    /// An integer arithmetic operation caused an overflow.
-    IntegerOverflow,
-
-    /// An integer division by zero.
-    IntegerDivisionByZero,
-
-    /// Failed float-to-int conversion.
-    BadConversionToInteger,
-
-    /// Code that was supposed to have been unreachable was reached.
-    UnreachableCodeReached,
-
-    /// Execution has potentially run too long and may be interrupted.
-    Interrupt,
-
-    /// When the `component-model` feature is enabled this trap represents a
-    /// function that was `canon lift`'d, then `canon lower`'d, then called.
-    /// This combination of creation of a function in the component model
-    /// generates a function that always traps and, when called, produces this
-    /// flavor of trap.
-    AlwaysTrapAdapter,
-
-    /// When wasm code is configured to consume fuel and it runs out of fuel
-    /// then this trap will be raised.
-    ///
-    /// For more information see
-    /// [`Config::consume_fuel`](crate::Config::consume_fuel).
-    OutOfFuel,
-}
+pub use wasmtime_environ::Trap;
 
-impl Trap {
-    // Same safety requirements and caveats as
-    // `wasmtime_runtime::raise_user_trap`.
-    pub(crate) unsafe fn raise(error: anyhow::Error) -> ! {
-        let needs_backtrace = error.downcast_ref::<WasmBacktrace>().is_none();
-        wasmtime_runtime::raise_user_trap(error, needs_backtrace)
-    }
+// Same safety requirements and caveats as
+// `wasmtime_runtime::raise_user_trap`.
+pub(crate) unsafe fn raise(error: anyhow::Error) -> ! {
+    let needs_backtrace = error.downcast_ref::<WasmBacktrace>().is_none();
+    wasmtime_runtime::raise_user_trap(error, needs_backtrace)
+}
 
-    #[cold] // traps are exceptional, this helps move handling off the main path
-    pub(crate) fn from_runtime_box(
-        store: &StoreOpaque,
-        runtime_trap: Box<wasmtime_runtime::Trap>,
-    ) -> Error {
-        let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap;
-        let (error, pc) = match reason {
-            // For user-defined errors they're already an `anyhow::Error` so no
-            // conversion is really necessary here, but a `backtrace` may have
-            // been captured so it's attempted to get inserted here.
-            //
-            // If the error is actually a `Trap` then the backtrace is inserted
-            // directly into the `Trap` since there's storage there for it.
-            // Otherwise though this represents a host-defined error which isn't
-            // using a `Trap` but instead some other condition that was fatal to
-            // wasm itself. In that situation the backtrace is inserted as
-            // contextual information on error using `error.context(...)` to
-            // provide useful information to debug with for the embedder/caller,
-            // otherwise the information about what the wasm was doing when the
-            // error was generated would be lost.
-            wasmtime_runtime::TrapReason::User {
-                error,
-                needs_backtrace,
-            } => {
-                debug_assert!(needs_backtrace == backtrace.is_some());
-                (error, None)
-            }
-            wasmtime_runtime::TrapReason::Jit(pc) => {
-                let code = store
-                    .modules()
-                    .lookup_trap_code(pc)
-                    .unwrap_or(TrapCode::StackOverflow);
-                (Trap::from_env(code).into(), Some(pc))
-            }
-            wasmtime_runtime::TrapReason::Wasm(trap_code) => {
-                (Trap::from_env(trap_code).into(), None)
-            }
-        };
-        match backtrace {
-            Some(bt) => {
-                let bt = WasmBacktrace::new(store, bt, pc);
-                if bt.wasm_trace.is_empty() {
-                    error
-                } else {
-                    error.context(bt)
-                }
-            }
-            None => error,
+#[cold] // traps are exceptional, this helps move handling off the main path
+pub(crate) fn from_runtime_box(
+    store: &StoreOpaque,
+    runtime_trap: Box<wasmtime_runtime::Trap>,
+) -> Error {
+    let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap;
+    let (error, pc) = match reason {
+        // For user-defined errors they're already an `anyhow::Error` so no
+        // conversion is really necessary here, but a `backtrace` may have
+        // been captured so it's attempted to get inserted here.
+        //
+        // If the error is actually a `Trap` then the backtrace is inserted
+        // directly into the `Trap` since there's storage there for it.
+        // Otherwise though this represents a host-defined error which isn't
+        // using a `Trap` but instead some other condition that was fatal to
+        // wasm itself. In that situation the backtrace is inserted as
+        // contextual information on error using `error.context(...)` to
+        // provide useful information to debug with for the embedder/caller,
+        // otherwise the information about what the wasm was doing when the
+        // error was generated would be lost.
+        wasmtime_runtime::TrapReason::User {
+            error,
+            needs_backtrace,
+        } => {
+            debug_assert!(needs_backtrace == backtrace.is_some());
+            (error, None)
         }
-    }
-
-    /// Panics if `code` is `TrapCode::User`.
-    pub(crate) fn from_env(code: TrapCode) -> Self {
-        match code {
-            TrapCode::StackOverflow => Trap::StackOverflow,
-            TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
-            TrapCode::HeapMisaligned => Trap::HeapMisaligned,
-            TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
-            TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
-            TrapCode::BadSignature => Trap::BadSignature,
-            TrapCode::IntegerOverflow => Trap::IntegerOverflow,
-            TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
-            TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
-            TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
-            TrapCode::Interrupt => Trap::Interrupt,
-            TrapCode::AlwaysTrapAdapter => Trap::AlwaysTrapAdapter,
+        wasmtime_runtime::TrapReason::Jit(pc) => {
+            let code = store
+                .modules()
+                .lookup_trap_code(pc)
+                .unwrap_or(Trap::StackOverflow);
+            (code.into(), Some(pc))
         }
+        wasmtime_runtime::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
+    };
+    match backtrace {
+        Some(bt) => {
+            let bt = WasmBacktrace::new(store, bt, pc);
+            if bt.wasm_trace.is_empty() {
+                error
+            } else {
+                error.context(bt)
+            }
+        }
+        None => error,
     }
 }
 
-impl fmt::Display for Trap {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use Trap::*;
-
-        let desc = match self {
-            StackOverflow => "call stack exhausted",
-            MemoryOutOfBounds => "out of bounds memory access",
-            HeapMisaligned => "misaligned memory access",
-            TableOutOfBounds => "undefined element: out of bounds table access",
-            IndirectCallToNull => "uninitialized element",
-            BadSignature => "indirect call type mismatch",
-            IntegerOverflow => "integer overflow",
-            IntegerDivisionByZero => "integer divide by zero",
-            BadConversionToInteger => "invalid conversion to integer",
-            UnreachableCodeReached => "wasm `unreachable` instruction executed",
-            Interrupt => "interrupt",
-            AlwaysTrapAdapter => "degenerate component adapter called",
-            OutOfFuel => "all fuel consumed by WebAssembly",
-        };
-        write!(f, "wasm trap: {desc}")
-    }
-}
-
-impl std::error::Error for Trap {}
-
 /// Representation of a backtrace of function frames in a WebAssembly module for
 /// where an error happened.
 ///
diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs
index 0a4754840..288724111 100644
--- a/crates/wast/src/wast.rs
+++ b/crates/wast/src/wast.rs
@@ -132,8 +132,8 @@ impl<T> WastContext<T> {
     }
 
     /// Register "spectest" which is used by the spec testsuite.
-    pub fn register_spectest(&mut self) -> Result<()> {
-        link_spectest(&mut self.core_linker, &mut self.store)?;
+    pub fn register_spectest(&mut self, use_shared_memory: bool) -> Result<()> {
+        link_spectest(&mut self.core_linker, &mut self.store, use_shared_memory)?;
         #[cfg(feature = "component-model")]
         link_component_spectest(&mut self.component_linker)?;
         Ok(())
diff --git a/src/commands/wast.rs b/src/commands/wast.rs
index 53cb37cde..fb9246241 100644
--- a/src/commands/wast.rs
+++ b/src/commands/wast.rs
@@ -36,7 +36,7 @@ impl WastCommand {
         let mut wast_context = WastContext::new(store);
 
         wast_context
-            .register_spectest()
+            .register_spectest(true)
             .expect("error instantiating \"spectest\"");
 
         for script in self.scripts.iter() {
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 429c59477..fc5c0f7ca 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -175,6 +175,16 @@ are only created when requested by the application, however, and otherwise
 only does its stated purpose.
 """
 
+[[audits.form_urlencoded]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.1.0"
+notes = """
+This is a small crate for working with url-encoded forms which doesn't have any
+more than what it says on the tin. Contains one `unsafe` block related to
+performance around utf-8 validation which is fairly easy to verify as correct.
+"""
+
 [[audits.heck]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -187,6 +197,17 @@ criteria = "safe-to-deploy"
 version = "2.2.1"
 notes = "I am the author of this crate."
 
+[[audits.idna]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.3.0"
+notes = """
+This is a crate without unsafe code or usage of the standard library. The large
+size of this crate comes from the large generated unicode tables file. This
+crate is broadly used throughout the ecosystem and does not contain anything
+suspicious.
+"""
+
 [[audits.indexmap-nostd]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-run"
@@ -280,6 +301,16 @@ criteria = "safe-to-deploy"
 version = "1.0.0"
 notes = "I am the author of this crate."
 
+[[audits.percent-encoding]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "2.2.0"
+notes = """
+This crate is a single-file crate that does what it says on the tin. There are
+a few `unsafe` blocks related to utf-8 validation which are locally verifiable
+as correct and otherwise this crate is good to go.
+"""
+
 [[audits.regalloc2]]
 who = "Jamey Sharp <jsharp@fastly.com>"
 criteria = "safe-to-deploy"
@@ -334,6 +365,60 @@ criteria = "safe-to-deploy"
 version = "0.23.0"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.tinyvec]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.6.0"
+notes = """
+This crate, while it implements collections, does so without `std::*` APIs and
+without `unsafe`. Skimming the crate everything looks reasonable and what one
+would expect from idiomatic safe collections in Rust.
+"""
+
+[[audits.tinyvec_macros]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.1.0"
+notes = """
+This is a trivial crate which only contains a singular macro definition which is
+intended to multiplex across the internal representation of a tinyvec,
+presumably. This trivially doesn't contain anything bad.
+"""
+
+[[audits.unicode-bidi]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.3.8"
+notes = """
+This crate has no unsafe code and does not use `std::*`. Skimming the crate it
+does not attempt to out of the bounds of what it's already supposed to be doing.
+"""
+
+[[audits.unicode-normalization]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.1.19"
+notes = """
+This crate contains one usage of `unsafe` which I have manually checked to see
+it as correct. This crate's size comes in large part due to the generated
+unicode tables that it contains. This crate is additionally widely used
+throughout the ecosystem and skimming the crate shows no usage of `std::*` APIs
+and nothing suspicious.
+"""
+
+[[audits.url]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "2.3.1"
+notes = """
+This crate contains no `unsafe` code and otherwise doesn't use any functionality
+it's not supposed to from `std` or such. This crate is the defacto standard for
+URL parsing in the Rust community with widespread usage to battle-test, harden,
+and suss out bugs. I've historically reviewed this crate in the past and it
+is similar to what it once was back then. Skimming over the crate there is
+nothing suspicious and it's everything you'd expect a Rust URL parser to be.
+"""
+
 [[audits.wasm-encoder]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -370,6 +455,12 @@ criteria = "safe-to-deploy"
 version = "0.19.0"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wasm-encoder]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.20.0"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wasm-encoder]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -418,6 +509,12 @@ criteria = "safe-to-run"
 version = "0.2.11"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wasm-mutate]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-run"
+version = "0.2.12"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wasm-smith]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -460,6 +557,27 @@ criteria = "safe-to-run"
 version = "0.11.8"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wasm-smith]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-run"
+version = "0.11.9"
+notes = "The Bytecode Alliance is the author of this crate."
+
+[[audits.wasmi]]
+who = "Robin Freyler <robin.freyler@gmail.com>"
+criteria = "safe-to-run"
+version = "0.20.0"
+notes = """
+I am the author of this crate. It contains unsafe Rust code.
+However, the crate does not read or write data from any parts of the filesystem,
+it does not install software upon compilation e.g. via build scripts,
+it does not connect to network endpoints and does not misuse system resources.
+
+If any of the above happens it is either by the user explicitly telling the
+crate to do so (it is an interpreter) or due to a bug or other unintended
+behavior.
+"""
+
 [[audits.wasmi_arena]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-run"
@@ -487,21 +605,6 @@ criteria = "safe-to-run"
 version = "0.5.0"
 notes = "See notes for version 0.4.0"
 
-[[audits.wasmi]]
-who = "Robin Freyler <robin.freyler@gmail.com>"
-criteria = "safe-to-run"
-version = "0.20.0"
-notes = """
-I am the author of this crate. It contains unsafe Rust code.
-However, the crate does not read or write data from any parts of the filesystem,
-it does not install software upon compilation e.g. via build scripts,
-it does not connect to network endpoints and does not misuse system resources.
-
-If any of the above happens it is either by the user explicitly telling the
-crate to do so (it is an interpreter) or due to a bug or other unintended
-behavior.
-"""
-
 [[audits.wasmparser]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -550,6 +653,12 @@ criteria = "safe-to-deploy"
 version = "0.94.0"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wasmparser]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.95.0"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wasmparser-nostd]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-run"
@@ -603,6 +712,12 @@ criteria = "safe-to-deploy"
 version = "0.2.43"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wasmprinter]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.2.44"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wast]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -651,6 +766,12 @@ criteria = "safe-to-deploy"
 version = "49.0.0"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wast]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "50.0.0"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wat]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
@@ -681,6 +802,12 @@ criteria = "safe-to-deploy"
 version = "1.0.51"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[audits.wat]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.0.52"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[audits.wat]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index e4d018c52..4411710d3 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -482,10 +482,6 @@ criteria = "safe-to-deploy"
 version = "0.6.5"
 criteria = "safe-to-deploy"
 
-[[exemptions.memory_units]]
-version = "0.3.0"
-criteria = "safe-to-run"
-
 [[exemptions.miette]]
 version = "5.1.0"
 criteria = "safe-to-deploy"
@@ -1022,10 +1018,6 @@ criteria = "safe-to-run"
 version = "0.2.80"
 criteria = "safe-to-run"
 
-[[exemptions.wasmi]]
-version = "0.19.0"
-criteria = "safe-to-run"
-
 [[exemptions.web-sys]]
 version = "0.3.57"
 criteria = "safe-to-run"
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 79a331a07..c89595997 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -353,6 +353,17 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
 criteria = "safe-to-run"
 delta = "0.1.27 -> 0.1.29"
 
+[[audits.mozilla.audits.unicode-normalization]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+delta = "0.1.19 -> 0.1.20"
+notes = "I am the author of most of these changes upstream, and prepared the release myself, at which point I looked at the other changes since 0.1.19."
+
+[[audits.mozilla.audits.unicode-normalization]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+delta = "0.1.20 -> 0.1.21"
+
 [[audits.mozilla.audits.wasm-encoder]]
 who = "Ryan Hunt <rhunt@eqrion.net>"
 criteria = "safe-to-deploy"
@@ -396,7 +407,6 @@ delta = "0.87.0 -> 0.88.0"
 who = "Ryan Hunt <rhunt@eqrion.net>"
 criteria = "safe-to-deploy"
 version = "44.0.0"
-notes = "Maintained by the Bytecode Alliance, with contributions from Mozilla. wast has no unsafe code and the only ambient capability it uses is to read the full contents of a file that is given to it."
 
 [[audits.mozilla.audits.wast]]
 who = "Yury Delendik <ydelendik@mozilla.com>"


Test patch:
diff --git a/cranelift/filetests/filetests/isa/s390x/arithmetic.clif b/cranelift/filetests/filetests/isa/s390x/arithmetic.clif
index 17b76ce55..d827f16d2 100644
--- a/cranelift/filetests/filetests/isa/s390x/arithmetic.clif
+++ b/cranelift/filetests/filetests/isa/s390x/arithmetic.clif
@@ -703,7 +703,7 @@ block0(v0: i128, v1: i128):
 
 ;   stmg %r7, %r15, 56(%r15)
 ; block0:
-;   lgr %r14, %r2
+;   lgr %r10, %r2
 ;   vl %v0, 0(%r3)
 ;   vl %v1, 0(%r4)
 ;   lgdr %r4, %f0
@@ -718,7 +718,7 @@ block0(v0: i128, v1: i128):
 ;   agrk %r4, %r2, %r8
 ;   agr %r5, %r4
 ;   vlvgp %v5, %r5, %r3
-;   lgr %r2, %r14
+;   lgr %r2, %r10
 ;   vst %v5, 0(%r2)
 ;   lmg %r7, %r15, 56(%r15)
 ;   br %r14
diff --git a/cranelift/filetests/filetests/isa/s390x/conversions.clif b/cranelift/filetests/filetests/isa/s390x/conversions.clif
index 3299aa0ec..fd47683e4 100644
--- a/cranelift/filetests/filetests/isa/s390x/conversions.clif
+++ b/cranelift/filetests/filetests/isa/s390x/conversions.clif
@@ -348,8 +348,8 @@ block0(v0: i128):
 
 ; block0:
 ;   vl %v0, 0(%r2)
-;   vgbm %v2, 0
-;   vceqgs %v4, %v0, %v2
+;   vgbm %v3, 0
+;   vceqgs %v5, %v0, %v3
 ;   lghi %r2, 0
 ;   locghine %r2, -1
 ;   br %r14
@@ -362,8 +362,8 @@ block0(v0: i128):
 
 ; block0:
 ;   vl %v0, 0(%r2)
-;   vgbm %v2, 0
-;   vceqgs %v4, %v0, %v2
+;   vgbm %v3, 0
+;   vceqgs %v5, %v0, %v3
 ;   lhi %r2, 0
 ;   lochine %r2, -1
 ;   br %r14
@@ -376,8 +376,8 @@ block0(v0: i128):
 
 ; block0:
 ;   vl %v0, 0(%r2)
-;   vgbm %v2, 0
-;   vceqgs %v4, %v0, %v2
+;   vgbm %v3, 0
+;   vceqgs %v5, %v0, %v3
 ;   lhi %r2, 0
 ;   lochine %r2, -1
 ;   br %r14
@@ -390,8 +390,8 @@ block0(v0: i128):
 
 ; block0:
 ;   vl %v0, 0(%r2)
-;   vgbm %v2, 0
-;   vceqgs %v4, %v0, %v2
+;   vgbm %v3, 0
+;   vceqgs %v5, %v0, %v3
 ;   lhi %r2, 0
 ;   lochine %r2, -1
 ;   br %r14
diff --git a/cranelift/filetests/filetests/isa/s390x/icmp-i128.clif b/cranelift/filetests/filetests/isa/s390x/icmp-i128.clif
index abe10f1a7..91e75492e 100644
--- a/cranelift/filetests/filetests/isa/s390x/icmp-i128.clif
+++ b/cranelift/filetests/filetests/isa/s390x/icmp-i128.clif
@@ -10,7 +10,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vceqgs %v4, %v0, %v1
+;   vceqgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochie %r2, 1
 ;   br %r14
@@ -24,7 +24,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vceqgs %v4, %v0, %v1
+;   vceqgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochine %r2, 1
 ;   br %r14
@@ -38,7 +38,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vecg %v0, %v1 ; jne 10 ; vchlgs %v4, %v1, %v0
+;   vecg %v0, %v1 ; jne 10 ; vchlgs %v5, %v1, %v0
 ;   lhi %r2, 0
 ;   lochil %r2, 1
 ;   br %r14
@@ -52,7 +52,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vecg %v1, %v0 ; jne 10 ; vchlgs %v4, %v0, %v1
+;   vecg %v1, %v0 ; jne 10 ; vchlgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochil %r2, 1
 ;   br %r14
@@ -66,7 +66,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vecg %v1, %v0 ; jne 10 ; vchlgs %v4, %v0, %v1
+;   vecg %v1, %v0 ; jne 10 ; vchlgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochinl %r2, 1
 ;   br %r14
@@ -80,7 +80,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   vecg %v0, %v1 ; jne 10 ; vchlgs %v4, %v1, %v0
+;   vecg %v0, %v1 ; jne 10 ; vchlgs %v5, %v1, %v0
 ;   lhi %r2, 0
 ;   lochinl %r2, 1
 ;   br %r14
@@ -94,7 +94,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   veclg %v0, %v1 ; jne 10 ; vchlgs %v4, %v1, %v0
+;   veclg %v0, %v1 ; jne 10 ; vchlgs %v5, %v1, %v0
 ;   lhi %r2, 0
 ;   lochil %r2, 1
 ;   br %r14
@@ -108,7 +108,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   veclg %v1, %v0 ; jne 10 ; vchlgs %v4, %v0, %v1
+;   veclg %v1, %v0 ; jne 10 ; vchlgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochil %r2, 1
 ;   br %r14
@@ -122,7 +122,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   veclg %v1, %v0 ; jne 10 ; vchlgs %v4, %v0, %v1
+;   veclg %v1, %v0 ; jne 10 ; vchlgs %v5, %v0, %v1
 ;   lhi %r2, 0
 ;   lochinl %r2, 1
 ;   br %r14
@@ -136,7 +136,7 @@ block0(v0: i128, v1: i128):
 ; block0:
 ;   vl %v0, 0(%r2)
 ;   vl %v1, 0(%r3)
-;   veclg %v0, %v1 ; jne 10 ; vchlgs %v4, %v1, %v0
+;   veclg %v0, %v1 ; jne 10 ; vchlgs %v5, %v1, %v0
 ;   lhi %r2, 0
 ;   lochinl %r2, 1
 ;   br %r14
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_no_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_no_spectre.clif
new file mode 100644
index 000000000..52081f193
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_no_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i32, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i32
+
+block0(v0: i64, v1: i32, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i32, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i32
+    fn0 = %do_store(i64, i32, i32)
+
+block0(v0: i64, v1: i32, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_yes_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_yes_spectre.clif
new file mode 100644
index 000000000..52081f193
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i32_yes_guards_yes_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i32, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i32
+
+block0(v0: i64, v1: i32, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i32, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i32
+    fn0 = %do_store(i64, i32, i32)
+
+block0(v0: i64, v1: i32, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_no_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_no_spectre.clif
new file mode 100644
index 000000000..db20c1168
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_no_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i64, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i64
+
+block0(v0: i64, v1: i64, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i64, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i64
+    fn0 = %do_store(i64, i64, i32)
+
+block0(v0: i64, v1: i64, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_yes_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_yes_spectre.clif
new file mode 100644
index 000000000..db20c1168
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_dynamic_i64_yes_guards_yes_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i64, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i64
+
+block0(v0: i64, v1: i64, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i64, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+8
+    heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0xffff_ffff, index_type i64
+    fn0 = %do_store(i64, i64, i32)
+
+block0(v0: i64, v1: i64, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_no_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_no_spectre.clif
new file mode 100644
index 000000000..42bd0eb49
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_no_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i32, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i32
+
+block0(v0: i64, v1: i32, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i32, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i32
+    fn0 = %do_store(i64, i32, i32)
+
+block0(v0: i64, v1: i32, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_yes_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_yes_spectre.clif
new file mode 100644
index 000000000..42bd0eb49
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_static_i32_yes_guards_yes_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i32, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i32
+
+block0(v0: i64, v1: i32, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i32, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i32
+    fn0 = %do_store(i64, i32, i32)
+
+block0(v0: i64, v1: i32, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_no_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_no_spectre.clif
new file mode 100644
index 000000000..dcb08f9c9
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_no_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i64, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i64
+
+block0(v0: i64, v1: i64, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i64, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i64
+    fn0 = %do_store(i64, i64, i32)
+
+block0(v0: i64, v1: i64, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_yes_spectre.clif b/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_yes_spectre.clif
new file mode 100644
index 000000000..dcb08f9c9
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/heap_load_store_static_i64_yes_guards_yes_spectre.clif
@@ -0,0 +1,40 @@
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=true
+
+function %do_store(i64 vmctx, i64, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i64
+
+block0(v0: i64, v1: i64, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, i64, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    
+    heap0 = static gv1, min 0x1000, bound 0x1000, offset_guard 0xffff_ffff, index_type i64
+    fn0 = %do_store(i64, i64, i32)
+
+block0(v0: i64, v1: i64, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
diff --git a/cranelift/filetests/filetests/runtests/make-heap-load-store-tests.sh b/cranelift/filetests/filetests/runtests/make-heap-load-store-tests.sh
new file mode 100755
index 000000000..694ee30ae
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/make-heap-load-store-tests.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+# This script generates the `heap_load_store_*.clif` test files.
+#
+# Usage:
+#
+#     $ ./make-heap-load-store-tests.sh
+
+set -e
+cd $(dirname "$0")
+
+function generate_one_test() {
+    local kind=$1
+    local index_type=$2
+    local guard=$3
+    local spectre=$4
+
+    local enable_spectre=true
+    if [[ spectre == "no" ]]; then
+        enable_spectre=false
+    fi
+
+    local have_guards=yes
+    if [[ guard == "0" ]]; then
+        have_guards=no
+    fi
+
+    local gv2=""
+    local bound=0x1000
+    if [[ $kind == "dynamic" ]]; then
+        gv2="gv2 = load.i64 notrap aligned gv0+8"
+        bound=gv2
+    fi
+
+    local filename="heap_load_store_${kind}_${index_type}_${have_guards}_guards_${spectre}_spectre.clif"
+    echo "Generating $filename"
+
+    cat <<EOF > "$filename"
+;; !!! GENERATED BY 'make-heap-load-store-tests.sh' DO NOT EDIT !!!
+
+test interpret
+;; test run
+;; target x86_64
+;; target s390x
+;; target aarch64
+;; target riscv64
+
+set enable_heap_access_spectre_mitigation=${enable_spectre}
+
+function %do_store(i64 vmctx, ${index_type}, i32) {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    $gv2
+    heap0 = ${kind} gv1, min 0x1000, bound ${bound}, offset_guard ${guard}, index_type ${index_type}
+
+block0(v0: i64, v1: ${index_type}, v2: i32):
+    heap_store.i32 heap0 little v1+4, v2
+    return
+}
+
+function %test(i64 vmctx, ${index_type}, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    $gv2
+    heap0 = ${kind} gv1, min 0x1000, bound ${bound}, offset_guard ${guard}, index_type ${index_type}
+    fn0 = %do_store(i64, ${index_type}, i32)
+
+block0(v0: i64, v1: ${index_type}, v2: i32):
+    call fn0(v0, v1, v2)
+    v3 = heap_load.i32 heap0 little v1+4
+    return v3
+}
+; heap: ${kind}, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; run: %test(0, 0) == 0
+; run: %test(0, -1) == -1
+; run: %test(16, 1) == 1
+; run: %test(16, -1) == -1
+; run: %test(2049, 0xaabb_ccdd) == 0xaabb_ccdd
+EOF
+}
+
+for spectre in yes no; do
+    for guard in 0 0xffff_ffff; do
+        for index_type in i32 i64; do
+            for kind in static dynamic; do
+                generate_one_test $kind $index_type $guard $spectre
+            done
+        done
+    done
+done
diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs
index 29b830091..f7ff59d55 100644
--- a/crates/wast/src/spectest.rs
+++ b/crates/wast/src/spectest.rs
@@ -3,7 +3,11 @@ use wasmtime::*;
 
 /// Return an instance implementing the "spectest" interface used in the
 /// spec testsuite.
-pub fn link_spectest<T>(linker: &mut Linker<T>, store: &mut Store<T>) -> Result<()> {
+pub fn link_spectest<T>(
+    linker: &mut Linker<T>,
+    store: &mut Store<T>,
+    use_shared_memory: bool,
+) -> Result<()> {
     linker.func_wrap("spectest", "print", || {})?;
     linker.func_wrap("spectest", "print_i32", |val: i32| println!("{}: i32", val))?;
     linker.func_wrap("spectest", "print_i64", |val: i64| println!("{}: i64", val))?;
@@ -42,6 +46,12 @@ pub fn link_spectest<T>(linker: &mut Linker<T>, store: &mut Store<T>) -> Result<
     let memory = Memory::new(&mut *store, ty)?;
     linker.define("spectest", "memory", memory)?;
 
+    if use_shared_memory {
+        let ty = MemoryType::shared(1, 1);
+        let memory = Memory::new(&mut *store, ty)?;
+        linker.define("spectest", "shared_memory", memory)?;
+    }
+
     Ok(())
 }
 
diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs
index e5a478733..43b42d4ff 100644
--- a/tests/all/component_model.rs
+++ b/tests/all/component_model.rs
@@ -27,7 +27,7 @@ fn components_importing_modules() -> Result<()> {
         &engine,
         r#"
         (component
-            (import "" (core module))
+            (import "a" (core module))
         )
         "#,
     )?;
@@ -36,7 +36,7 @@ fn components_importing_modules() -> Result<()> {
         &engine,
         r#"
         (component
-            (import "" (core module $m1
+            (import "a" (core module $m1
                 (import "" "" (func))
                 (import "" "x" (global i32))
 
diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs
index 49f3937c4..e91eb8690 100644
--- a/tests/all/component_model/aot.rs
+++ b/tests/all/component_model/aot.rs
@@ -88,12 +88,12 @@ fn cannot_serialize_exported_module() -> Result<()> {
         &engine,
         r#"(component
             (core module $m)
-            (export "" (core module $m))
+            (export "a" (core module $m))
         )"#,
     )?;
     let mut store = Store::new(&engine, ());
     let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
-    let module = instance.get_module(&mut store, "").unwrap();
+    let module = instance.get_module(&mut store, "a").unwrap();
     assert!(module.serialize().is_err());
     Ok(())
 }
diff --git a/tests/all/component_model/dynamic.rs b/tests/all/component_model/dynamic.rs
index fd9e19c5c..e24ac80ff 100644
--- a/tests/all/component_model/dynamic.rs
+++ b/tests/all/component_model/dynamic.rs
@@ -400,7 +400,7 @@ fn everything() -> Result<()> {
             r#"
             (record
                 (field "A" u32)
-                (field "B" (enum "1" "2"))
+                (field "B" (enum "a" "b"))
                 (field "C" (record (field "D" bool) (field "E" u32)))
                 (field "F" (list (flags "G" "H" "I")))
                 (field "J" (variant
@@ -464,7 +464,7 @@ fn everything() -> Result<()> {
     let f_element_type = &f_type.unwrap_list().ty();
     let input = ty.unwrap_record().new_val([
         ("A", Val::U32(32343)),
-        ("B", b_type.unwrap_enum().new_val("2")?),
+        ("B", b_type.unwrap_enum().new_val("b")?),
         (
             "C",
             c_type
diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs
index fded264cb..6751be24c 100644
--- a/tests/all/component_model/func.rs
+++ b/tests/all/component_model/func.rs
@@ -2411,7 +2411,7 @@ fn run_export_with_internal_adapter() -> Result<()> {
     (func (export "add-five") (type $t) (canon lift (core func $m "add-five")))
   )
   (component $b
-    (import "interface-0.1.0" (instance $i
+    (import "interface-v1" (instance $i
       (export "add-five" (func (type $t)))))
     (core module $m
       (func $add-five (import "interface-0.1.0" "add-five") (param i32) (result i32))
@@ -2428,7 +2428,7 @@ fn run_export_with_internal_adapter() -> Result<()> {
     (export "run" (func 1))
   )
   (instance $a (instantiate $a))
-  (instance $b (instantiate $b (with "interface-0.1.0" (instance $a))))
+  (instance $b (instantiate $b (with "interface-v1" (instance $a))))
   (export "run" (func $b "run"))
 )
 "#;
diff --git a/tests/all/component_model/import.rs b/tests/all/component_model/import.rs
index 9f2654ba7..ae771a646 100644
--- a/tests/all/component_model/import.rs
+++ b/tests/all/component_model/import.rs
@@ -18,7 +18,7 @@ fn can_compile() -> Result<()> {
     Component::new(
         &engine,
         r#"(component
-            (import "" (func $f))
+            (import "a" (func $f))
             (core func (canon lower (func $f)))
         )"#,
     )?;
@@ -26,7 +26,7 @@ fn can_compile() -> Result<()> {
         &engine,
         format!(
             r#"(component
-                (import "" (func $f (param "a" string)))
+                (import "a" (func $f (param "a" string)))
                 {libc}
                 (core func (canon lower (func $f) (memory $libc "memory") (realloc (func $libc "realloc"))))
             )"#
@@ -84,7 +84,7 @@ fn can_compile() -> Result<()> {
 fn simple() -> Result<()> {
     let component = r#"
         (component
-            (import "" (func $log (param "a" string)))
+            (import "a" (func $log (param "a" string)))
 
             (core module $libc
                 (memory (export "memory") 1)
@@ -126,7 +126,7 @@ fn simple() -> Result<()> {
 
     let mut linker = Linker::new(&engine);
     linker.root().func_wrap(
-        "",
+        "a",
         |mut store: StoreContextMut<'_, Option<String>>, (arg,): (WasmStr,)| -> Result<_> {
             let s = arg.to_str(&store)?.to_string();
             assert!(store.data().is_none());
@@ -146,7 +146,7 @@ fn simple() -> Result<()> {
     let mut linker = Linker::new(&engine);
     linker.root().func_new(
         &component,
-        "",
+        "a",
         |mut store: StoreContextMut<'_, Option<String>>, args, _results| {
             if let Val::String(s) = &args[0] {
                 assert!(store.data().is_none());
diff --git a/tests/all/component_model/strings.rs b/tests/all/component_model/strings.rs
index 7f85b67fe..21a9ff283 100644
--- a/tests/all/component_model/strings.rs
+++ b/tests/all/component_model/strings.rs
@@ -214,14 +214,14 @@ fn test_ptr_out_of_bounds(engine: &Engine, src: &str, dst: &str) -> Result<()> {
       (memory (export "memory") 1)
     )
     (core instance $m (instantiate $m))
-    (func (export "") (param "a" string)
+    (func (export "a") (param "a" string)
       (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory")
         string-encoding={dst})
     )
   )
 
   (component $c2
-    (import "" (func $f (param "a" string)))
+    (import "a" (func $f (param "a" string)))
     (core module $libc
       (memory (export "memory") 1)
     )
@@ -237,7 +237,7 @@ fn test_ptr_out_of_bounds(engine: &Engine, src: &str, dst: &str) -> Result<()> {
   )
 
   (instance $c (instantiate $c))
-  (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+  (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
 )
 "#
         );
@@ -282,14 +282,14 @@ fn test_ptr_overflow(engine: &Engine, src: &str, dst: &str) -> Result<()> {
       (memory (export "memory") 1)
     )
     (core instance $m (instantiate $m))
-    (func (export "") (param "a" string)
+    (func (export "a") (param "a" string)
       (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory")
         string-encoding={dst})
     )
   )
 
   (component $c2
-    (import "" (func $f (param "a" string)))
+    (import "a" (func $f (param "a" string)))
     (core module $libc
       (memory (export "memory") 1)
     )
@@ -305,7 +305,7 @@ fn test_ptr_overflow(engine: &Engine, src: &str, dst: &str) -> Result<()> {
   )
 
   (instance $c (instantiate $c))
-  (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+  (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   (export "f" (func $c2 "f"))
 )
 "#
@@ -387,14 +387,14 @@ fn test_realloc_oob(engine: &Engine, src: &str, dst: &str) -> Result<()> {
       (memory (export "memory") 1)
     )
     (core instance $m (instantiate $m))
-    (func (export "") (param "a" string)
+    (func (export "a") (param "a" string)
       (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory")
         string-encoding={dst})
     )
   )
 
   (component $c2
-    (import "" (func $f (param "a" string)))
+    (import "a" (func $f (param "a" string)))
     (core module $libc
       (memory (export "memory") 1)
     )
@@ -410,7 +410,7 @@ fn test_realloc_oob(engine: &Engine, src: &str, dst: &str) -> Result<()> {
   )
 
   (instance $c (instantiate $c))
-  (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+  (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   (export "f" (func $c2 "f"))
 )
 "#
@@ -534,14 +534,14 @@ fn test_raw_when_encoded(
       (memory (export "memory") 1)
     )
     (core instance $m (instantiate $m))
-    (func (export "") (param "a" string)
+    (func (export "a") (param "a" string)
       (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory")
         string-encoding={dst})
     )
   )
 
   (component $c2
-    (import "" (func $f (param "a" string)))
+    (import "a" (func $f (param "a" string)))
     (core module $libc
       (memory (export "memory") 1)
       (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 0)
@@ -560,7 +560,7 @@ fn test_raw_when_encoded(
   )
 
   (instance $c (instantiate $c))
-  (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+  (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   (export "f" (func $c2 "f"))
 )
 "#
diff --git a/tests/all/main.rs b/tests/all/main.rs
index 19390ced0..9b613ffb1 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -31,6 +31,7 @@ mod store;
 mod table;
 mod threads;
 mod traps;
+mod wait_notify;
 mod wast;
 
 /// A helper to compile a module in a new store with reference types enabled.
diff --git a/tests/all/wait_notify.rs b/tests/all/wait_notify.rs
new file mode 100644
index 000000000..930b49744
--- /dev/null
+++ b/tests/all/wait_notify.rs
@@ -0,0 +1,120 @@
+use anyhow::Result;
+use std::time::Instant;
+use wasmtime::*;
+
+#[test]
+fn atomic_wait_timeout_length() -> Result<()> {
+    let sleep_nanoseconds = 500000000;
+    let wat = format!(
+        r#"(module
+        (import "env" "memory" (memory 1 1 shared))
+
+        (func (export "func1") (result i32)
+            (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const {sleep_nanoseconds}))
+        )
+
+        (data (i32.const 0) "\00\00\00\00")
+    )"#
+    );
+    let mut config = Config::new();
+    config.wasm_threads(true);
+    let engine = Engine::new(&config)?;
+    let module = Module::new(&engine, wat)?;
+    let mut store = Store::new(&engine, ());
+    let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 1))?;
+    let instance = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
+    let now = Instant::now();
+    let func_ret = instance
+        .get_typed_func::<(), i32>(&mut store, "func1")
+        .unwrap()
+        .call(&mut store, ())
+        .unwrap();
+    let duration = now.elapsed();
+    assert!(
+        duration.as_nanos() >= sleep_nanoseconds,
+        "duration: {duration:?} < {sleep_nanoseconds:?}"
+    );
+    assert_eq!(func_ret, 2);
+    Ok(())
+}
+
+#[test]
+fn atomic_wait_notify_basic() -> Result<()> {
+    let wat = r#"(module
+        (import "env" "memory" (memory 1 1 shared))
+
+        (func (export "first_thread") (result i32)
+            (drop (memory.atomic.wait32 (i32.const 4) (i32.const 0) (i64.const -1)))
+            (i32.atomic.store (i32.const 0) (i32.const 42))
+            (drop (memory.atomic.notify (i32.const 0) (i32.const -1)))
+            (i32.atomic.load (i32.const 0))
+        )
+
+        (func (export "second_thread") (result i32)
+            (i32.atomic.store (i32.const 4) (i32.const 21))
+            (drop (memory.atomic.notify (i32.const 4) (i32.const -1)))
+            (drop (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const -1)))
+            (i32.atomic.load (i32.const 0))
+        )
+
+        (data (i32.const 0) "\00\00\00\00")
+        (data (i32.const 4) "\00\00\00\00")
+    )"#;
+    let mut config = Config::new();
+    config.wasm_threads(true);
+    let engine = Engine::new(&config)?;
+    let module = Module::new(&engine, wat)?;
+    let mut store = Store::new(&engine, ());
+    let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 1))?;
+    let instance1 = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
+
+    let thread = {
+        let engine = engine.clone();
+        let module = module.clone();
+        let shared_memory = shared_memory.clone();
+        std::thread::spawn(move || {
+            let mut store = Store::new(&engine, ());
+            let instance2 = Instance::new(&mut store, &module, &[shared_memory.into()]).unwrap();
+
+            let instance2_first_word = instance2
+                .get_typed_func::<(), i32>(&mut store, "second_thread")
+                .unwrap()
+                .call(&mut store, ())
+                .unwrap();
+
+            assert_eq!(instance2_first_word, 42);
+        })
+    };
+
+    let instance1_first_word = instance1
+        .get_typed_func::<(), i32>(&mut store, "first_thread")
+        .unwrap()
+        .call(&mut store, ())
+        .unwrap();
+    assert_eq!(instance1_first_word, 42);
+
+    thread.join().unwrap();
+
+    let data = shared_memory.data();
+    // Verify that the memory is the same in all shared locations.
+    let shared_memory_first_word = i32::from_le_bytes(unsafe {
+        [
+            *data[0].get(),
+            *data[1].get(),
+            *data[2].get(),
+            *data[3].get(),
+        ]
+    });
+    assert_eq!(shared_memory_first_word, 42);
+
+    let shared_memory_second_word = i32::from_le_bytes(unsafe {
+        [
+            *data[4].get(),
+            *data[5].get(),
+            *data[6].get(),
+            *data[7].get(),
+        ]
+    });
+    assert_eq!(shared_memory_second_word, 21);
+    Ok(())
+}
diff --git a/tests/all/wast.rs b/tests/all/wast.rs
index 27ea94b91..5f274b5be 100644
--- a/tests/all/wast.rs
+++ b/tests/all/wast.rs
@@ -1,9 +1,12 @@
+use anyhow::Context;
+use bstr::ByteSlice;
 use once_cell::sync::Lazy;
 use std::path::Path;
 use std::sync::{Condvar, Mutex};
 use wasmtime::{
     Config, Engine, InstanceAllocationStrategy, PoolingAllocationConfig, Store, Strategy,
 };
+use wasmtime_environ::WASM_PAGE_SIZE;
 use wasmtime_wast::WastContext;
 
 include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
@@ -14,20 +17,32 @@ include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
 fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> {
     drop(env_logger::try_init());
 
+    let wast_bytes = std::fs::read(wast).with_context(|| format!("failed to read `{}`", wast))?;
+
     match strategy {
         Strategy::Cranelift => {}
         _ => unimplemented!(),
     }
+
     let wast = Path::new(wast);
 
     let memory64 = feature_found(wast, "memory64");
     let multi_memory = feature_found(wast, "multi-memory");
     let threads = feature_found(wast, "threads");
+    let reference_types = !(threads && feature_found(wast, "proposals"));
+    let use_shared_memory = feature_found_src(&wast_bytes, "shared_memory")
+        || feature_found_src(&wast_bytes, "shared)");
+
+    if pooling && use_shared_memory {
+        eprintln!("skipping pooling test with shared memory");
+        return Ok(());
+    }
 
     let mut cfg = Config::new();
     cfg.wasm_multi_memory(multi_memory)
         .wasm_threads(threads)
         .wasm_memory64(memory64)
+        .wasm_reference_types(reference_types)
         .cranelift_debug_verifier(true);
 
     cfg.wasm_component_model(feature_found(wast, "component-model"));
@@ -62,7 +77,11 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
         // Don't use 4gb address space reservations when not hogging memory, and
         // also don't reserve lots of memory after dynamic memories for growth
         // (makes growth slower).
-        cfg.static_memory_maximum_size(0);
+        if use_shared_memory {
+            cfg.static_memory_maximum_size(2 * WASM_PAGE_SIZE as u64);
+        } else {
+            cfg.static_memory_maximum_size(0);
+        }
         cfg.dynamic_memory_reserved_for_growth(0);
     }
 
@@ -91,8 +110,10 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
 
     let store = Store::new(&Engine::new(&cfg)?, ());
     let mut wast_context = WastContext::new(store);
-    wast_context.register_spectest()?;
-    wast_context.run_file(wast)?;
+
+    wast_context.register_spectest(use_shared_memory)?;
+    wast_context.run_buffer(wast.to_str().unwrap(), &wast_bytes)?;
+
     Ok(())
 }
 
@@ -103,6 +124,10 @@ fn feature_found(path: &Path, name: &str) -> bool {
     })
 }
 
+fn feature_found_src(bytes: &[u8], name: &str) -> bool {
+    bytes.contains_str(name)
+}
+
 // The pooling tests make about 6TB of address space reservation which means
 // that we shouldn't let too many of them run concurrently at once. On
 // high-cpu-count systems (e.g. 80 threads) this leads to mmap failures because
diff --git a/tests/misc_testsuite/component-model/fused.wast b/tests/misc_testsuite/component-model/fused.wast
index 4f3a9d8ab..c80548ccb 100644
--- a/tests/misc_testsuite/component-model/fused.wast
+++ b/tests/misc_testsuite/component-model/fused.wast
@@ -7,7 +7,7 @@
   (func $foo (canon lift (core func $m "")))
 
   (component $c
-    (import "" (func $foo))
+    (import "a" (func $foo))
 
     (core func $foo (canon lower (func $foo)))
     (core module $m2
@@ -17,7 +17,7 @@
     (core instance $m2 (instantiate $m2 (with "" (instance (export "" (func $foo))))))
   )
 
-  (instance $c (instantiate $c (with "" (func $foo))))
+  (instance $c (instantiate $c (with "a" (func $foo))))
 )
 
 ;; boolean parameters
@@ -835,7 +835,7 @@
     (func (export "s16") (param "a" s16) (canon lift (core func $m "s")))
   )
   (component $c2
-    (import "" (instance $i
+    (import "a" (instance $i
       (export "u8" (func (param "a" u8)))
       (export "s8" (func (param "a" s8)))
       (export "u16" (func (param "a" u16)))
@@ -878,7 +878,7 @@
     ))
   )
   (instance $c1 (instantiate $c1))
-  (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+  (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
 )
 
 ;; translation of locals between different types
@@ -956,7 +956,7 @@
     (func (export "e") (type $func_e) (canon lift (core func $m "e")))
   )
   (component $c2
-    (import "" (instance $i
+    (import "a" (instance $i
       (export "a" (func (type $func_a)))
       (export "b" (func (type $func_b)))
       (export "c" (func (type $func_c)))
@@ -1008,7 +1008,7 @@
     ))
   )
   (instance $c1 (instantiate $c1))
-  (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+  (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
 )
 
 ;; different size variants
@@ -1063,7 +1063,7 @@
     (func (export "a") (param "x" u8) (param "a" $a) (canon lift (core func $m "a")))
   )
   (component $c2
-    (import "" (instance $i
+    (import "a" (instance $i
       (export "a" (func (param "x" u8) (param "a" $a)))
     ))
 
@@ -1111,7 +1111,7 @@
     ))
   )
   (instance $c1 (instantiate $c1))
-  (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+  (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
 )
 
 ;; roundtrip some valid chars
@@ -1124,7 +1124,7 @@
     (func (export "a") (param "a" char) (result char) (canon lift (core func $m "a")))
   )
   (component $c2
-    (import "" (instance $i
+    (import "a" (instance $i
       (export "a" (func (param "a" char) (result char)))
     ))
 
@@ -1158,7 +1158,7 @@
     (func (export "roundtrip") (param "a" char) (canon lift (core func $m "roundtrip")))
   )
   (instance $c1 (instantiate $c1))
-  (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+  (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
 
   (export "roundtrip" (func $c2 "roundtrip"))
 )
@@ -1176,7 +1176,7 @@
       (func (export "a") (param "a" char) (canon lift (core func $m "a")))
     )
     (component $c2
-      (import "" (instance $i (export "a" (func (param "a" char)))))
+      (import "a" (instance $i (export "a" (func (param "a" char)))))
       (core func $a (canon lower (func $i "a")))
       (core module $m
         (import "" "a" (func $a (param i32)))
@@ -1186,7 +1186,7 @@
       (core instance (instantiate $m (with "" (instance (export "a" (func $a))))))
     )
     (instance $c1 (instantiate $c1))
-    (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+    (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
   )
   "unreachable")
 (assert_trap
@@ -1197,7 +1197,7 @@
       (func (export "a") (param "a" char) (canon lift (core func $m "a")))
     )
     (component $c2
-      (import "" (instance $i (export "a" (func (param "a" char)))))
+      (import "a" (instance $i (export "a" (func (param "a" char)))))
       (core func $a (canon lower (func $i "a")))
       (core module $m
         (import "" "a" (func $a (param i32)))
@@ -1207,7 +1207,7 @@
       (core instance (instantiate $m (with "" (instance (export "a" (func $a))))))
     )
     (instance $c1 (instantiate $c1))
-    (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+    (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
   )
   "unreachable")
 (assert_trap
@@ -1218,7 +1218,7 @@
       (func (export "a") (param "a" char) (canon lift (core func $m "a")))
     )
     (component $c2
-      (import "" (instance $i (export "a" (func (param "a" char)))))
+      (import "a" (instance $i (export "a" (func (param "a" char)))))
       (core func $a (canon lower (func $i "a")))
       (core module $m
         (import "" "a" (func $a (param i32)))
@@ -1228,7 +1228,7 @@
       (core instance (instantiate $m (with "" (instance (export "a" (func $a))))))
     )
     (instance $c1 (instantiate $c1))
-    (instance $c2 (instantiate $c2 (with "" (instance $c1))))
+    (instance $c2 (instantiate $c2 (with "a" (instance $c1))))
   )
   "unreachable")
 
@@ -1332,7 +1332,7 @@
   (instance $c1 (instantiate $c1))
 
   (component $c2
-    (import "" (instance $i
+    (import "a" (instance $i
       (export "f0" (func (param "a" $f0)))
       (export "f1" (func (param "a" $f1)))
       (export "f8" (func (param "a" $f8)))
@@ -1397,7 +1397,7 @@
       ))
     ))
   )
-  (instance (instantiate $c2 (with "" (instance $c1))))
+  (instance (instantiate $c2 (with "a" (instance $c1))))
 )
 
 ;; Adapters are used slightly out-of-order here to stress the internals of
diff --git a/tests/misc_testsuite/component-model/instance.wast b/tests/misc_testsuite/component-model/instance.wast
index d23fe605f..d3128acd3 100644
--- a/tests/misc_testsuite/component-model/instance.wast
+++ b/tests/misc_testsuite/component-model/instance.wast
@@ -70,7 +70,7 @@
 ;; Test to see if a component with a type export can be instantiated.
 (component
     (type string)
-    (export "" (type 0))
+    (export "a" (type 0))
 )
 
 ;; double-check the start function runs by ensuring that a trap shows up and it
diff --git a/tests/misc_testsuite/component-model/nested.wast b/tests/misc_testsuite/component-model/nested.wast
index 6373c287e..af81e9304 100644
--- a/tests/misc_testsuite/component-model/nested.wast
+++ b/tests/misc_testsuite/component-model/nested.wast
@@ -330,8 +330,8 @@
   )
 
   (component $c1
-    (component $c2 (export "")
-      (component $c3 (export "")
+    (component $c2 (export "a")
+      (component $c3 (export "a")
         (alias outer $C $m (core module $my_module))
         (alias outer $C $c (component $my_component))
 
@@ -342,8 +342,8 @@
   )
 
   (instance $i1 (instantiate $c1))
-  (instance $i2 (instantiate (component $i1 "")))
-  (instance $i3 (instantiate (component $i2 "")))
+  (instance $i2 (instantiate (component $i1 "a")))
+  (instance $i3 (instantiate (component $i2 "a")))
 
   (core instance $m1 (instantiate (module $i3 "m")))
   (instance $c (instantiate (component $i3 "c")))
@@ -412,10 +412,10 @@
 
   ;; thread the host function through an instance
   (component $c
-    (import "" (func $f (result u32)))
+    (import "a" (func $f (result u32)))
     (export "f" (func $f))
   )
-  (instance $c (instantiate $c (with "" (func $import))))
+  (instance $c (instantiate $c (with "a" (func $import))))
   (alias export $c "f" (func $import2))
 
   ;; thread the host function into a nested component
diff --git a/tests/misc_testsuite/component-model/simple.wast b/tests/misc_testsuite/component-model/simple.wast
index df4088b94..7bf2ab1e8 100644
--- a/tests/misc_testsuite/component-model/simple.wast
+++ b/tests/misc_testsuite/component-model/simple.wast
@@ -23,20 +23,20 @@
 
 (assert_invalid
   (component
-    (import "" (component))
+    (import "a" (component))
   )
   "root-level component imports are not supported")
 
 (assert_invalid
   (component
-    (component (export ""))
+    (component (export "a"))
   )
   "exporting a component from the root component is not supported")
 
 (component
   (core module $m (func (export "")))
   (core instance $m (instantiate $m))
-  (func (export "") (canon lift (core func $m "")))
+  (func (export "a") (canon lift (core func $m "")))
 )
 
-(assert_return (invoke ""))
+(assert_return (invoke "a"))
diff --git a/tests/misc_testsuite/component-model/strings.wast b/tests/misc_testsuite/component-model/strings.wast
index 8583d038a..2bc0a5063 100644
--- a/tests/misc_testsuite/component-model/strings.wast
+++ b/tests/misc_testsuite/component-model/strings.wast
@@ -8,13 +8,13 @@
         (memory (export "memory") 1)
       )
       (core instance $m (instantiate $m))
-      (func (export "") (param "a" string)
+      (func (export "a") (param "a" string)
         (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory"))
       )
     )
 
     (component $c2
-      (import "" (func $f (param "a" string)))
+      (import "a" (func $f (param "a" string)))
       (core module $libc
         (memory (export "memory") 1)
       )
@@ -30,7 +30,7 @@
     )
 
     (instance $c (instantiate $c))
-    (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+    (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   )
   "unreachable")
 
@@ -44,13 +44,13 @@
         (memory (export "memory") 1)
       )
       (core instance $m (instantiate $m))
-      (func (export "") (param "a" string)
+      (func (export "a") (param "a" string)
         (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory"))
       )
     )
 
     (component $c2
-      (import "" (func $f (param "a" string)))
+      (import "a" (func $f (param "a" string)))
       (core module $libc
         (memory (export "memory") 1)
       )
@@ -66,7 +66,7 @@
     )
 
     (instance $c (instantiate $c))
-    (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+    (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   )
   "unreachable")
 
@@ -80,14 +80,14 @@
         (memory (export "memory") 1)
       )
       (core instance $m (instantiate $m))
-      (func (export "") (param "a" string)
+      (func (export "a") (param "a" string)
         (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory")
           string-encoding=utf8)
       )
     )
 
     (component $c2
-      (import "" (func $f (param "a" string)))
+      (import "a" (func $f (param "a" string)))
       (core module $libc
         (memory (export "memory") 1)
       )
@@ -103,6 +103,6 @@
     )
 
     (instance $c (instantiate $c))
-    (instance $c2 (instantiate $c2 (with "" (func $c ""))))
+    (instance $c2 (instantiate $c2 (with "a" (func $c "a"))))
   )
   "unreachable")
diff --git a/tests/misc_testsuite/component-model/types.wast b/tests/misc_testsuite/component-model/types.wast
index 4405726f1..2cd334a62 100644
--- a/tests/misc_testsuite/component-model/types.wast
+++ b/tests/misc_testsuite/component-model/types.wast
@@ -60,7 +60,7 @@
   (type (instance))
 
   (type (component
-    (import "" (func (type $empty)))
+    (import "x" (func (type $empty)))
     (import "y" (func))
     (import "z" (component))
 
@@ -71,7 +71,7 @@
   ))
 
   (type (instance
-    (export "" (func (type $empty)))
+    (export "x" (func (type $empty)))
     (export "y" (func))
     (export "z" (component))
 
@@ -97,7 +97,7 @@
 
   (component $C2
     (alias outer $C $f (core type $my_f))
-    (import "" (core module (type $m)))
+    (import "a" (core module (type $m)))
     (import "x" (core module
       (alias outer $C2 $my_f (type $my_f))
       (import "" "1" (func (type $my_f)))
diff --git a/tests/misc_testsuite/threads/atomics_notify.wast b/tests/misc_testsuite/threads/atomics_notify.wast
new file mode 100644
index 000000000..d37db9915
--- /dev/null
+++ b/tests/misc_testsuite/threads/atomics_notify.wast
@@ -0,0 +1,18 @@
+;; From https://github.com/bytecodealliance/wasmtime/pull/5255
+;;
+
+(module
+  (memory 1 1)
+  (func (export "notify") (result i32) (memory.atomic.notify (i32.const 0) (i32.const -1)))
+)
+
+;; notify returns 0 on unshared memories
+(assert_return (invoke "notify") (i32.const 0))
+
+(module
+  (memory 1 1 shared)
+  (func (export "notify_shared") (result i32) (memory.atomic.notify (i32.const 0) (i32.const -1)))
+)
+
+;; notify returns 0 with 0 waiters
+(assert_return (invoke "notify_shared") (i32.const 0))
diff --git a/tests/misc_testsuite/threads/atomics_wait_address.wast b/tests/misc_testsuite/threads/atomics_wait_address.wast
index edffdc554..d594e4ee2 100644
--- a/tests/misc_testsuite/threads/atomics_wait_address.wast
+++ b/tests/misc_testsuite/threads/atomics_wait_address.wast
@@ -12,7 +12,7 @@
   (export "main" (func $main))
 )
 
-(assert_trap (invoke "main") "misaligned memory access")
+(assert_trap (invoke "main") "unaligned atomic")
 
 
 (module
@@ -48,5 +48,96 @@
   (export "wait64" (func $wait64))
 )
 
-(assert_trap (invoke "wait32") "misaligned memory access")
-(assert_trap (invoke "wait64") "misaligned memory access")
+(assert_trap (invoke "wait32") "unaligned atomic")
+(assert_trap (invoke "wait64") "unaligned atomic")
+
+(module
+  (type (;0;) (func))
+  (func $wait32 (type 0)
+    i32.const 0
+    i32.const 42
+    i64.const 0
+    memory.atomic.wait32
+    unreachable)
+  (func $wait64 (type 0)
+    i32.const 0
+    i64.const 43
+    i64.const 0
+    memory.atomic.wait64
+    unreachable)
+  (memory (;0;) 4 4)
+  (export "wait32" (func $wait32))
+  (export "wait64" (func $wait64))
+)
+
+(assert_trap (invoke "wait32") "atomic wait on non-shared memory")
+(assert_trap (invoke "wait64") "atomic wait on non-shared memory")
+
+;; not valid values for memory.atomic.wait
+(module
+  (memory 1 1 shared)
+  (type (;0;) (func))
+  (func $wait32 (result i32)
+    i32.const 0
+    i32.const 42
+    i64.const -1
+    memory.atomic.wait32
+    )
+  (func $wait64 (result i32)
+    i32.const 0
+    i64.const 43
+    i64.const -1
+    memory.atomic.wait64
+    )
+  (export "wait32" (func $wait32))
+  (export "wait64" (func $wait64))
+)
+
+(assert_return (invoke "wait32") (i32.const 1))
+(assert_return (invoke "wait64") (i32.const 1))
+
+;; timeout
+(module
+  (memory 1 1 shared)
+  (type (;0;) (func))
+  (func $wait32 (result i32)
+    i32.const 0
+    i32.const 0
+    i64.const 1000
+    memory.atomic.wait32
+    )
+  (func $wait64 (result i32)
+    i32.const 0
+    i64.const 0
+    i64.const 1000
+    memory.atomic.wait64
+    )
+  (export "wait32" (func $wait32))
+  (export "wait64" (func $wait64))
+)
+
+(assert_return (invoke "wait32") (i32.const 2))
+(assert_return (invoke "wait64") (i32.const 2))
+
+;; timeout on 0ns
+(module
+  (memory 1 1 shared)
+  (type (;0;) (func))
+  (func $wait32 (result i32)
+    i32.const 0
+    i32.const 0
+    i64.const 0
+    memory.atomic.wait32
+    )
+  (func $wait64 (result i32)
+    i32.const 0
+    i64.const 0
+    i64.const 0
+    memory.atomic.wait64
+    )
+  (export "wait32" (func $wait32))
+  (export "wait64" (func $wait64))
+)
+
+(assert_return (invoke "wait32") (i32.const 2))
+(assert_return (invoke "wait64") (i32.const 2))
diff --git a/tests/misc_testsuite/threads/load-store-alignment.wast b/tests/misc_testsuite/threads/load-store-alignment.wast
index f95a8531b..5c16d0e79 100644
--- a/tests/misc_testsuite/threads/load-store-alignment.wast
+++ b/tests/misc_testsuite/threads/load-store-alignment.wast
@@ -80,19 +80,19 @@
 
 ;; misaligned loads
 (assert_return (invoke "32.load8u" (i32.const 1)) (i32.const 0))
-(assert_trap (invoke "32.load16u" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "32.load32u" (i32.const 1)) "misaligned memory access")
+(assert_trap (invoke "32.load16u" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "32.load32u" (i32.const 1)) "unaligned atomic")
 (assert_return (invoke "64.load8u" (i32.const 1)) (i64.const 0))
-(assert_trap (invoke "64.load16u" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "64.load32u" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "64.load64u" (i32.const 1)) "misaligned memory access")
+(assert_trap (invoke "64.load16u" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "64.load32u" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "64.load64u" (i32.const 1)) "unaligned atomic")
 (assert_return (invoke "32.load8u o1" (i32.const 0)) (i32.const 0))
-(assert_trap (invoke "32.load16u o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "32.load32u o1" (i32.const 0)) "misaligned memory access")
+(assert_trap (invoke "32.load16u o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "32.load32u o1" (i32.const 0)) "unaligned atomic")
 (assert_return (invoke "64.load8u o1" (i32.const 0)) (i64.const 0))
-(assert_trap (invoke "64.load16u o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "64.load32u o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "64.load64u o1" (i32.const 0)) "misaligned memory access")
+(assert_trap (invoke "64.load16u o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "64.load32u o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "64.load64u o1" (i32.const 0)) "unaligned atomic")
 
 ;; aligned stores
 (assert_return (invoke "32.store8" (i32.const 0)))
@@ -111,16 +111,16 @@
 
 ;; misaligned stores
 (assert_return (invoke "32.store8" (i32.const 1)))
-(assert_trap (invoke "32.store16" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "32.store32" (i32.const 1)) "misaligned memory access")
+(assert_trap (invoke "32.store16" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "32.store32" (i32.const 1)) "unaligned atomic")
 (assert_return (invoke "64.store8" (i32.const 1)))
-(assert_trap (invoke "64.store16" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "64.store32" (i32.const 1)) "misaligned memory access")
-(assert_trap (invoke "64.store64" (i32.const 1)) "misaligned memory access")
+(assert_trap (invoke "64.store16" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "64.store32" (i32.const 1)) "unaligned atomic")
+(assert_trap (invoke "64.store64" (i32.const 1)) "unaligned atomic")
 (assert_return (invoke "32.store8 o1" (i32.const 0)))
-(assert_trap (invoke "32.store16 o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "32.store32 o1" (i32.const 0)) "misaligned memory access")
+(assert_trap (invoke "32.store16 o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "32.store32 o1" (i32.const 0)) "unaligned atomic")
 (assert_return (invoke "64.store8 o1" (i32.const 0)))
-(assert_trap (invoke "64.store16 o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "64.store32 o1" (i32.const 0)) "misaligned memory access")
-(assert_trap (invoke "64.store64 o1" (i32.const 0)) "misaligned memory access")
+(assert_trap (invoke "64.store16 o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "64.store32 o1" (i32.const 0)) "unaligned atomic")
+(assert_trap (invoke "64.store64 o1" (i32.const 0)) "unaligned atomic")