Repository: bytecodealliance/wasmtime
Pull request #4374 (2022-07-04 08:28:14)

[AArch64] Port min/max to ISLE
Copyright (c) 2022, Arm Limited.

<!--

Please ensure that the following steps are all taken care of before submitting
the PR.

- [ ] This has been discussed in issue #..., or if not, please tell us why
  here.
- [ ] A short description of what this does, why it is needed; if the
  description becomes long, the matter should probably be discussed in an issue
  first.
- [ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
  If you don't know who could review this, please indicate so. The list of
  suggested reviewers on the right can help you.

Please ensure all communication adheres to the [code of
conduct](https://github.com/bytecodealliance/wasmtime/blob/master/CODE_OF_CONDUCT.md).
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index f3e125def..4717e16e9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -450,6 +450,15 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "component-macro-test"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "console"
 version = "0.15.0"
@@ -491,7 +500,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "cranelift-frontend",
@@ -499,14 +508,14 @@ dependencies = [
 
 [[package]]
 name = "cranelift-bforest"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-entity",
 ]
 
 [[package]]
 name = "cranelift-codegen"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "bincode",
  "cranelift-bforest",
@@ -528,18 +537,18 @@ dependencies = [
 
 [[package]]
 name = "cranelift-codegen-meta"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen-shared",
 ]
 
 [[package]]
 name = "cranelift-codegen-shared"
-version = "0.86.0"
+version = "0.87.0"
 
 [[package]]
 name = "cranelift-entity"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "serde",
 ]
@@ -568,7 +577,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-frontend"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "hashbrown",
@@ -588,7 +597,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-interpreter"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "cranelift-entity",
@@ -601,7 +610,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-isle"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "log",
  "miette",
@@ -610,7 +619,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-jit"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "anyhow",
  "cranelift",
@@ -629,7 +638,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-module"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "anyhow",
  "cranelift-codegen",
@@ -638,7 +647,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-native"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "libc",
@@ -647,7 +656,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-object"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "anyhow",
  "cranelift-codegen",
@@ -661,14 +670,14 @@ dependencies = [
 
 [[package]]
 name = "cranelift-preopt"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
 ]
 
 [[package]]
 name = "cranelift-reader"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "smallvec",
@@ -677,7 +686,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-serde"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "clap 3.1.15",
  "cranelift-codegen",
@@ -721,7 +730,7 @@ dependencies = [
 
 [[package]]
 name = "cranelift-wasm"
-version = "0.86.0"
+version = "0.87.0"
 dependencies = [
  "cranelift-codegen",
  "cranelift-entity",
@@ -3080,7 +3089,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasi-cap-std-sync"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -3103,7 +3112,7 @@ dependencies = [
 
 [[package]]
 name = "wasi-common"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "bitflags",
@@ -3148,7 +3157,7 @@ dependencies = [
 
 [[package]]
 name = "wasi-tokio"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "cap-std",
@@ -3309,7 +3318,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -3385,7 +3394,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-cache"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "base64",
@@ -3408,11 +3417,12 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-cli"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "async-trait",
  "clap 3.1.15",
+ "component-macro-test",
  "criterion",
  "env_logger 0.9.0",
  "filecheck",
@@ -3448,7 +3458,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-cli-flags"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "clap 3.1.15",
@@ -3460,7 +3470,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-component-macro"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3469,7 +3479,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-cranelift"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "cranelift-codegen",
@@ -3489,7 +3499,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-environ"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "cranelift-entity",
@@ -3507,7 +3517,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-fiber"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "backtrace",
  "cc",
@@ -3559,7 +3569,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-jit"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "addr2line",
  "anyhow",
@@ -3584,7 +3594,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-jit-debug"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "lazy_static",
  "object",
@@ -3593,7 +3603,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-runtime"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "backtrace",
@@ -3618,7 +3628,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-types"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "cranelift-entity",
  "serde",
@@ -3628,7 +3638,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wasi"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "wasi-cap-std-sync",
@@ -3640,7 +3650,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wasi-crypto"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "wasi-crypto",
@@ -3650,7 +3660,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wasi-nn"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "openvino",
@@ -3661,7 +3671,7 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wast"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "wasmtime",
@@ -3721,7 +3731,7 @@ dependencies = [
 
 [[package]]
 name = "wiggle"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -3738,7 +3748,7 @@ dependencies = [
 
 [[package]]
 name = "wiggle-generate"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "anyhow",
  "heck",
@@ -3751,7 +3761,7 @@ dependencies = [
 
 [[package]]
 name = "wiggle-macro"
-version = "0.39.0"
+version = "0.40.0"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 706b3aeb9..74e73f195 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-cli"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Command-line interface for Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -21,15 +21,15 @@ path = "src/bin/wasmtime.rs"
 doc = false
 
 [dependencies]
-wasmtime = { path = "crates/wasmtime", version = "0.39.0", default-features = false, features = ['cache', 'cranelift'] }
-wasmtime-cache = { path = "crates/cache", version = "=0.39.0" }
-wasmtime-cli-flags = { path = "crates/cli-flags", version = "=0.39.0" }
-wasmtime-cranelift = { path = "crates/cranelift", version = "=0.39.0" }
-wasmtime-environ = { path = "crates/environ", version = "=0.39.0" }
-wasmtime-wast = { path = "crates/wast", version = "=0.39.0" }
-wasmtime-wasi = { path = "crates/wasi", version = "0.39.0" }
-wasmtime-wasi-crypto = { path = "crates/wasi-crypto", version = "0.39.0", optional = true }
-wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "0.39.0", optional = true }
+wasmtime = { path = "crates/wasmtime", version = "0.40.0", default-features = false, features = ['cache', 'cranelift'] }
+wasmtime-cache = { path = "crates/cache", version = "=0.40.0" }
+wasmtime-cli-flags = { path = "crates/cli-flags", version = "=0.40.0" }
+wasmtime-cranelift = { path = "crates/cranelift", version = "=0.40.0" }
+wasmtime-environ = { path = "crates/environ", version = "=0.40.0" }
+wasmtime-wast = { path = "crates/wast", version = "=0.40.0" }
+wasmtime-wasi = { path = "crates/wasi", version = "0.40.0" }
+wasmtime-wasi-crypto = { path = "crates/wasi-crypto", version = "0.40.0", optional = true }
+wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "0.40.0", optional = true }
 clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] }
 anyhow = "1.0.19"
 target-lexicon = { version = "0.12.0", default-features = false }
@@ -43,7 +43,7 @@ rustix = { version = "0.35.6", features = ["mm", "param"] }
 
 [dev-dependencies]
 # depend again on wasmtime to activate its default features for tests
-wasmtime = { path = "crates/wasmtime", version = "0.39.0" }
+wasmtime = { path = "crates/wasmtime", version = "0.40.0" }
 env_logger = "0.9.0"
 filecheck = "0.5.0"
 more-asserts = "0.2.1"
@@ -60,6 +60,7 @@ async-trait = "0.1"
 wat = "1.0.43"
 once_cell = "1.9.0"
 rayon = "1.5.0"
+component-macro-test = { path = "crates/misc/component-macro-test" }
 
 [target.'cfg(windows)'.dev-dependencies]
 windows-sys = { version = "0.36.0", features = ["Win32_System_Memory"] }
diff --git a/RELEASES.md b/RELEASES.md
index d2620d21a..7349857e6 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,5 +1,15 @@
 --------------------------------------------------------------------------------
 
+## 0.40.0
+
+Unreleased.
+
+### Added
+
+### Changed
+
+--------------------------------------------------------------------------------
+
 ## 0.39.0
 
 Unreleased.
@@ -37,6 +47,10 @@ Unreleased.
   It's recommended to only used the set of named `[features]` for Wasmtime.
   [#4293](https://github.com/bytecodealliance/wasmtime/pull/4293)
 
+* Register allocation has fixed a few issues related to excessive memory usage
+  at compile time.
+  [#4324](https://github.com/bytecodealliance/wasmtime/pull/4324)
+
 ### Fixed
 
 * A refactor of `Config` was made to fix an issue that the order of calls to `Config`
@@ -48,6 +62,10 @@ Unreleased.
   `simd` feature is disabled in `Config`.
   [#4231](https://github.com/bytecodealliance/wasmtime/pull/4231)
 
+* Generation of platform-specific unwinding information is disabled if
+  `wasm_backtrace` and `wasm_reference_types` are both disabled.
+  [#4351](https://github.com/bytecodealliance/wasmtime/pull/4351)
+
 --------------------------------------------------------------------------------
 
 ## 0.38.1
diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml
index 04999a53a..9f15c8796 100644
--- a/cranelift/Cargo.toml
+++ b/cranelift/Cargo.toml
@@ -15,19 +15,19 @@ path = "src/clif-util.rs"
 
 [dependencies]
 cfg-if = "1.0"
-cranelift-codegen = { path = "codegen", version = "0.86.0" }
-cranelift-entity = { path = "entity", version = "0.86.0" }
-cranelift-interpreter = { path = "interpreter", version = "0.86.0" }
-cranelift-reader = { path = "reader", version = "0.86.0" }
-cranelift-frontend = { path = "frontend", version = "0.86.0" }
-cranelift-wasm = { path = "wasm", version = "0.86.0", optional = true }
-cranelift-native = { path = "native", version = "0.86.0" }
+cranelift-codegen = { path = "codegen", version = "0.87.0" }
+cranelift-entity = { path = "entity", version = "0.87.0" }
+cranelift-interpreter = { path = "interpreter", version = "0.87.0" }
+cranelift-reader = { path = "reader", version = "0.87.0" }
+cranelift-frontend = { path = "frontend", version = "0.87.0" }
+cranelift-wasm = { path = "wasm", version = "0.87.0", optional = true }
+cranelift-native = { path = "native", version = "0.87.0" }
 cranelift-filetests = { path = "filetests", version = "0.73.0" }
-cranelift-module = { path = "module", version = "0.86.0" }
-cranelift-object = { path = "object", version = "0.86.0" }
-cranelift-jit = { path = "jit", version = "0.86.0" }
-cranelift-preopt = { path = "preopt", version = "0.86.0" }
-cranelift = { path = "umbrella", version = "0.86.0" }
+cranelift-module = { path = "module", version = "0.87.0" }
+cranelift-object = { path = "object", version = "0.87.0" }
+cranelift-jit = { path = "jit", version = "0.87.0" }
+cranelift-preopt = { path = "preopt", version = "0.87.0" }
+cranelift = { path = "umbrella", version = "0.87.0" }
 filecheck = "0.5.0"
 log = "0.4.8"
 termcolor = "1.1.2"
diff --git a/cranelift/bforest/Cargo.toml b/cranelift/bforest/Cargo.toml
index 006d87bae..870607a86 100644
--- a/cranelift/bforest/Cargo.toml
+++ b/cranelift/bforest/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-bforest"
-version = "0.86.0"
+version = "0.87.0"
 description = "A forest of B+-trees"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-bforest"
@@ -12,7 +12,7 @@ keywords = ["btree", "forest", "set", "map"]
 edition = "2021"
 
 [dependencies]
-cranelift-entity = { path = "../entity", version = "0.86.0", default-features = false }
+cranelift-entity = { path = "../entity", version = "0.87.0", default-features = false }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml
index 5cfdb07e8..23e631cc7 100644
--- a/cranelift/codegen/Cargo.toml
+++ b/cranelift/codegen/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-codegen"
-version = "0.86.0"
+version = "0.87.0"
 description = "Low-level code generator library"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-codegen"
@@ -13,9 +13,9 @@ build = "build.rs"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen-shared = { path = "./shared", version = "0.86.0" }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
-cranelift-bforest = { path = "../bforest", version = "0.86.0" }
+cranelift-codegen-shared = { path = "./shared", version = "0.87.0" }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
+cranelift-bforest = { path = "../bforest", version = "0.87.0" }
 hashbrown = { version = "0.11", optional = true }
 target-lexicon = "0.12"
 log = { version = "0.4.6", default-features = false }
@@ -34,8 +34,8 @@ souper-ir = { version = "2.1.0", optional = true }
 criterion = "0.3"
 
 [build-dependencies]
-cranelift-codegen-meta = { path = "meta", version = "0.86.0" }
-cranelift-isle = { path = "../isle/isle", version = "=0.86.0" }
+cranelift-codegen-meta = { path = "meta", version = "0.87.0" }
+cranelift-isle = { path = "../isle/isle", version = "=0.87.0" }
 miette = { version = "4.7.0", features = ["fancy"], optional = true }
 
 [features]
diff --git a/cranelift/codegen/meta/Cargo.toml b/cranelift/codegen/meta/Cargo.toml
index af0268f91..5716637b7 100644
--- a/cranelift/codegen/meta/Cargo.toml
+++ b/cranelift/codegen/meta/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "cranelift-codegen-meta"
 authors = ["The Cranelift Project Developers"]
-version = "0.86.0"
+version = "0.87.0"
 description = "Metaprogram for cranelift-codegen code generator library"
 license = "Apache-2.0 WITH LLVM-exception"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -13,7 +13,7 @@ edition = "2021"
 # rustdoc-args = [ "--document-private-items" ]
 
 [dependencies]
-cranelift-codegen-shared = { path = "../shared", version = "0.86.0" }
+cranelift-codegen-shared = { path = "../shared", version = "0.87.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/codegen/shared/Cargo.toml b/cranelift/codegen/shared/Cargo.toml
index 0626daa85..40cb546f9 100644
--- a/cranelift/codegen/shared/Cargo.toml
+++ b/cranelift/codegen/shared/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-codegen-shared"
-version = "0.86.0"
+version = "0.87.0"
 description = "For code shared between cranelift-codegen-meta and cranelift-codegen"
 license = "Apache-2.0 WITH LLVM-exception"
 repository = "https://github.com/bytecodealliance/wasmtime"
diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs
index 704a6a78e..59e32be9d 100644
--- a/cranelift/codegen/src/ir/immediates.rs
+++ b/cranelift/codegen/src/ir/immediates.rs
@@ -12,6 +12,7 @@ use core::str::FromStr;
 use core::{i32, u32};
 #[cfg(feature = "enable-serde")]
 use serde::{Deserialize, Serialize};
+use std::ops::Neg;
 
 /// Convert a type into a vector of bytes; all implementors in this file must use little-endian
 /// orderings of bytes to match WebAssembly's little-endianness.
@@ -761,18 +762,39 @@ impl Ieee32 {
 
     /// Check if the value is a NaN.
     pub fn is_nan(&self) -> bool {
-        f32::from_bits(self.0).is_nan()
+        self.as_f32().is_nan()
+    }
+
+    /// Converts Self to a rust f32
+    pub fn as_f32(self) -> f32 {
+        f32::from_bits(self.0)
+    }
+
+    /// Fused multiply-add. Computes (self * a) + b with only one rounding error, yielding a
+    /// more accurate result than an unfused multiply-add.
+    pub fn mul_add(&self, a: Self, b: Self) -> Self {
+        Self::with_float(self.as_f32().mul_add(a.as_f32(), b.as_f32()))
     }
 
     /// Returns the square root of self.
     pub fn sqrt(self) -> Self {
-        Self::with_float(f32::from_bits(self.0).sqrt())
+        Self::with_float(self.as_f32().sqrt())
+    }
+
+    /// Computes the absolute value of self.
+    pub fn abs(self) -> Self {
+        Self::with_float(self.as_f32().abs())
+    }
+
+    /// Returns a number composed of the magnitude of self and the sign of sign.
+    pub fn copysign(self, sign: Self) -> Self {
+        Self::with_float(self.as_f32().copysign(sign.as_f32()))
     }
 }
 
 impl PartialOrd for Ieee32 {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        f32::from_bits(self.0).partial_cmp(&f32::from_bits(other.0))
+        self.as_f32().partial_cmp(&other.as_f32())
     }
 }
 
@@ -806,6 +828,14 @@ impl IntoBytes for Ieee32 {
     }
 }
 
+impl Neg for Ieee32 {
+    type Output = Ieee32;
+
+    fn neg(self) -> Self::Output {
+        Self::with_float(self.as_f32().neg())
+    }
+}
+
 impl Ieee64 {
     /// Create a new `Ieee64` containing the bits of `x`.
     pub fn with_bits(x: u64) -> Self {
@@ -851,18 +881,39 @@ impl Ieee64 {
     /// Check if the value is a NaN. For [Ieee64], this means checking that the 11 exponent bits are
     /// all set.
     pub fn is_nan(&self) -> bool {
-        f64::from_bits(self.0).is_nan()
+        self.as_f64().is_nan()
+    }
+
+    /// Converts Self to a rust f64
+    pub fn as_f64(self) -> f64 {
+        f64::from_bits(self.0)
+    }
+
+    /// Fused multiply-add. Computes (self * a) + b with only one rounding error, yielding a
+    /// more accurate result than an unfused multiply-add.
+    pub fn mul_add(&self, a: Self, b: Self) -> Self {
+        Self::with_float(self.as_f64().mul_add(a.as_f64(), b.as_f64()))
     }
 
     /// Returns the square root of self.
     pub fn sqrt(self) -> Self {
-        Self::with_float(f64::from_bits(self.0).sqrt())
+        Self::with_float(self.as_f64().sqrt())
+    }
+
+    /// Computes the absolute value of self.
+    pub fn abs(self) -> Self {
+        Self::with_float(self.as_f64().abs())
+    }
+
+    /// Returns a number composed of the magnitude of self and the sign of sign.
+    pub fn copysign(self, sign: Self) -> Self {
+        Self::with_float(self.as_f64().copysign(sign.as_f64()))
     }
 }
 
 impl PartialOrd for Ieee64 {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        f64::from_bits(self.0).partial_cmp(&f64::from_bits(other.0))
+        self.as_f64().partial_cmp(&other.as_f64())
     }
 }
 
@@ -902,6 +953,14 @@ impl IntoBytes for Ieee64 {
     }
 }
 
+impl Neg for Ieee64 {
+    type Output = Ieee64;
+
+    fn neg(self) -> Self::Output {
+        Self::with_float(self.as_f64().neg())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/cranelift/codegen/src/isa/aarch64/inst.isle b/cranelift/codegen/src/isa/aarch64/inst.isle
index e57223a8f..578db8e5c 100644
--- a/cranelift/codegen/src/isa/aarch64/inst.isle
+++ b/cranelift/codegen/src/isa/aarch64/inst.isle
@@ -925,6 +925,7 @@
 (rule (vector_size (multi_lane 8 16)) (VectorSize.Size8x16))
 (rule (vector_size (multi_lane 16 4)) (VectorSize.Size16x4))
 (rule (vector_size (multi_lane 16 8)) (VectorSize.Size16x8))
+(rule (vector_size (multi_lane 32 2)) (VectorSize.Size32x2))
 (rule (vector_size (multi_lane 32 4)) (VectorSize.Size32x4))
 (rule (vector_size (multi_lane 64 2)) (VectorSize.Size64x2))
 
diff --git a/cranelift/codegen/src/isa/aarch64/lower.isle b/cranelift/codegen/src/isa/aarch64/lower.isle
index feb8eee83..2f5ffba80 100644
--- a/cranelift/codegen/src/isa/aarch64/lower.isle
+++ b/cranelift/codegen/src/isa/aarch64/lower.isle
@@ -499,6 +499,20 @@
             (result Reg (msub $I64 div y64 x64)))
         result))
 
+;;; Rules for integer min/max: umin, imin, umax, imax ;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(rule (lower (has_type ty @ (not_i64x2) (imin x y)))
+      (vec_rrr (VecALUOp.Smin) x y (vector_size ty)))
+
+(rule (lower (has_type ty @ (not_i64x2) (umin x y)))
+      (vec_rrr (VecALUOp.Umin) x y (vector_size ty)))
+
+(rule (lower (has_type ty @ (not_i64x2) (imax x y)))
+      (vec_rrr (VecALUOp.Smax) x y (vector_size ty)))
+
+(rule (lower (has_type ty @ (not_i64x2) (umax x y)))
+      (vec_rrr (VecALUOp.Umax) x y (vector_size ty)))
+
 ;;;; Rules for `uextend` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;; General rule for extending input to an output which fits in a single
diff --git a/cranelift/codegen/src/isa/aarch64/lower_inst.rs b/cranelift/codegen/src/isa/aarch64/lower_inst.rs
index e255af98d..30bb0c46e 100644
--- a/cranelift/codegen/src/isa/aarch64/lower_inst.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower_inst.rs
@@ -1245,34 +1245,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
             ctx.emit(Inst::gen_move(dst.regs()[1], src_hi, I64));
         }
 
-        Opcode::Imax | Opcode::Umax | Opcode::Umin | Opcode::Imin => {
-            let ty = ty.unwrap();
-
-            if !ty.is_vector() || ty.lane_bits() == 64 {
-                return Err(CodegenError::Unsupported(format!(
-                    "{}: Unsupported type: {:?}",
-                    op, ty
-                )));
-            }
-
-            let alu_op = match op {
-                Opcode::Umin => VecALUOp::Umin,
-                Opcode::Imin => VecALUOp::Smin,
-                Opcode::Umax => VecALUOp::Umax,
-                Opcode::Imax => VecALUOp::Smax,
-                _ => unreachable!(),
-            };
-            let rd = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
-            let rn = put_input_in_reg(ctx, inputs[0], NarrowValueMode::None);
-            let rm = put_input_in_reg(ctx, inputs[1], NarrowValueMode::None);
-            ctx.emit(Inst::VecRRR {
-                alu_op,
-                rd,
-                rn,
-                rm,
-                size: VectorSize::from_ty(ty),
-            });
-        }
+        Opcode::Imax | Opcode::Umax | Opcode::Umin | Opcode::Imin => implemented_in_isle(ctx),
 
         Opcode::IaddPairwise => implemented_in_isle(ctx),
 
diff --git a/cranelift/entity/Cargo.toml b/cranelift/entity/Cargo.toml
index c931136c8..10c88dd7a 100644
--- a/cranelift/entity/Cargo.toml
+++ b/cranelift/entity/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-entity"
-version = "0.86.0"
+version = "0.87.0"
 description = "Data structures using entity references as mapping keys"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-entity"
diff --git a/cranelift/frontend/Cargo.toml b/cranelift/frontend/Cargo.toml
index 82e04fc9d..20bd91e5d 100644
--- a/cranelift/frontend/Cargo.toml
+++ b/cranelift/frontend/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-frontend"
-version = "0.86.0"
+version = "0.87.0"
 description = "Cranelift IR builder helper"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-frontend"
@@ -11,7 +11,7 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
 target-lexicon = "0.12"
 log = { version = "0.4.6", default-features = false }
 hashbrown = { version = "0.11", optional = true }
diff --git a/cranelift/fuzzgen/Cargo.toml b/cranelift/fuzzgen/Cargo.toml
index c39ad48fd..139fed1b3 100644
--- a/cranelift/fuzzgen/Cargo.toml
+++ b/cranelift/fuzzgen/Cargo.toml
@@ -11,7 +11,7 @@ publish = false
 
 
 [dependencies]
-cranelift = { path = "../umbrella", version = "0.86.0" }
+cranelift = { path = "../umbrella", version = "0.87.0" }
 
 anyhow = "1.0.19"
 arbitrary = "1.0.0"
diff --git a/cranelift/interpreter/Cargo.toml b/cranelift/interpreter/Cargo.toml
index b97f9c9ed..4003495cb 100644
--- a/cranelift/interpreter/Cargo.toml
+++ b/cranelift/interpreter/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-interpreter"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Interpret Cranelift IR"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -11,15 +11,15 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0" }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0" }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
 log = { version = "0.4.8", default-features = false }
 smallvec = "1.6.1"
 thiserror = "1.0.15"
 
 [dev-dependencies]
-cranelift-frontend = { path = "../frontend", version = "0.86.0" }
-cranelift-reader = { path = "../reader", version = "0.86.0" }
+cranelift-frontend = { path = "../frontend", version = "0.87.0" }
+cranelift-reader = { path = "../reader", version = "0.87.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs
index 5b1925d7e..504af716f 100644
--- a/cranelift/interpreter/src/interpreter.rs
+++ b/cranelift/interpreter/src/interpreter.rs
@@ -8,12 +8,16 @@ use crate::frame::Frame;
 use crate::instruction::DfgInstructionContext;
 use crate::state::{MemoryError, State};
 use crate::step::{step, ControlFlow, StepError};
-use crate::value::ValueError;
+use crate::value::{Value, ValueError};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
-use cranelift_codegen::ir::{Block, FuncRef, Function, StackSlot, Type, Value as ValueRef};
+use cranelift_codegen::ir::{
+    ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, StackSlot, Type,
+    Value as ValueRef,
+};
 use log::trace;
 use std::collections::HashSet;
+use std::convert::{TryFrom, TryInto};
 use std::fmt::Debug;
 use std::iter;
 use thiserror::Error;
@@ -172,6 +176,21 @@ pub enum InterpreterError {
     FuelExhausted,
 }
 
+pub type HeapBacking = Vec<u8>;
+
+/// Represents a registered heap with an interpreter.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct HeapId(u32);
+
+/// Options for initializing a heap memory region
+#[derive(Debug)]
+pub enum HeapInit {
+    /// A zero initialized heap with `size` bytes
+    Zeroed(usize),
+    /// Initializes the heap with the backing memory unchanged.
+    FromBacking(HeapBacking),
+}
+
 /// Maintains the [Interpreter]'s state, implementing the [State] trait.
 pub struct InterpreterState<'a> {
     pub functions: FunctionStore<'a>,
@@ -179,7 +198,7 @@ pub struct InterpreterState<'a> {
     /// Number of bytes from the bottom of the stack where the current frame's stack space is
     pub frame_offset: usize,
     pub stack: Vec<u8>,
-    pub heap: Vec<u8>,
+    pub heaps: Vec<HeapBacking>,
     pub iflags: HashSet<IntCC>,
     pub fflags: HashSet<FloatCC>,
 }
@@ -191,7 +210,7 @@ impl Default for InterpreterState<'_> {
             frame_stack: vec![],
             frame_offset: 0,
             stack: Vec::with_capacity(1024),
-            heap: vec![0; 1024],
+            heaps: Vec::new(),
             iflags: HashSet::new(),
             fflags: HashSet::new(),
         }
@@ -203,6 +222,57 @@ impl<'a> InterpreterState<'a> {
         Self { functions, ..self }
     }
 
+    /// Registers a static heap and returns a reference to it
+    ///
+    /// This heap reference can be used to generate a heap pointer, which
+    /// can be used inside the interpreter to load / store values into the heap.
+    ///
+    /// ```rust
+    /// # use cranelift_codegen::ir::types::I64;
+    /// # use cranelift_interpreter::interpreter::{InterpreterState, HeapInit};
+    /// let mut state = InterpreterState::default();
+    /// let heap0 = state.register_heap(HeapInit::Zeroed(1024));
+    ///
+    /// let backing = Vec::from([10u8; 24]);
+    /// let heap1 = state.register_heap(HeapInit::FromBacking(backing));
+    /// ```
+    pub fn register_heap(&mut self, init: HeapInit) -> HeapId {
+        let heap_id = HeapId(self.heaps.len() as u32);
+
+        self.heaps.push(match init {
+            HeapInit::Zeroed(size) => iter::repeat(0).take(size).collect(),
+            HeapInit::FromBacking(backing) => backing,
+        });
+
+        heap_id
+    }
+
+    /// Returns a heap address that can be used inside the interpreter
+    ///
+    /// ```rust
+    /// # use cranelift_codegen::ir::types::I64;
+    /// # use cranelift_interpreter::interpreter::{InterpreterState, HeapInit};
+    /// let mut state = InterpreterState::default();
+    /// let heap_id = state.register_heap(HeapInit::Zeroed(1024));
+    /// let heap_base = state.get_heap_address(I64, heap_id, 0);
+    /// let heap_bound = state.get_heap_address(I64, heap_id, 1024);
+    /// ```
+    pub fn get_heap_address(
+        &self,
+        ty: Type,
+        heap_id: HeapId,
+        offset: u64,
+    ) -> Result<DataValue, MemoryError> {
+        let size = AddressSize::try_from(ty)?;
+        let heap_id = heap_id.0 as u64;
+        let addr = Address::from_parts(size, AddressRegion::Heap, heap_id, offset)?;
+
+        self.validate_address(&addr)?;
+        let dv = addr.try_into()?;
+
+        Ok(dv)
+    }
+
     fn current_frame_mut(&mut self) -> &mut Frame<'a> {
         let num_frames = self.frame_stack.len();
         match num_frames {
@@ -310,23 +380,54 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
         Address::from_parts(size, AddressRegion::Stack, 0, final_offset)
     }
 
-    fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result<Address, MemoryError> {
-        unimplemented!()
+    /// Builds an [Address] for the [Heap] referenced in the currently executing function.
+    ///
+    /// A CLIF Heap is essentially a GlobalValue and some metadata about that memory
+    /// region, such as bounds. Since heaps are based on Global Values it means that
+    /// once that GV is resolved we can essentially end up anywhere in memory.
+    ///
+    /// To build an [Address] we perform GV resolution, and try to ensure that we end up
+    /// in a valid region of memory.
+    fn heap_address(
+        &self,
+        size: AddressSize,
+        heap: Heap,
+        offset: u64,
+    ) -> Result<Address, MemoryError> {
+        let heap_data = &self.get_current_function().heaps[heap];
+        let heap_base = self.resolve_global_value(heap_data.base)?;
+        let mut addr = Address::try_from(heap_base)?;
+        addr.size = size;
+        addr.offset += offset;
+
+        // After resolving the address can point anywhere, we need to check if it's
+        // still valid.
+        self.validate_address(&addr)?;
+
+        Ok(addr)
     }
 
     fn checked_load(&self, addr: Address, ty: Type) -> Result<DataValue, MemoryError> {
         let load_size = ty.bytes() as usize;
+        let addr_start = addr.offset as usize;
+        let addr_end = addr_start + load_size;
 
         let src = match addr.region {
             AddressRegion::Stack => {
-                let addr_start = addr.offset as usize;
-                let addr_end = addr_start + load_size;
                 if addr_end > self.stack.len() {
                     return Err(MemoryError::OutOfBoundsLoad { addr, load_size });
                 }
 
                 &self.stack[addr_start..addr_end]
             }
+            AddressRegion::Heap => {
+                let heap_mem = match self.heaps.get(addr.entry as usize) {
+                    Some(mem) if addr_end <= mem.len() => mem,
+                    _ => return Err(MemoryError::OutOfBoundsLoad { addr, load_size }),
+                };
+
+                &heap_mem[addr_start..addr_end]
+            }
             _ => unimplemented!(),
         };
 
@@ -335,28 +436,172 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
 
     fn checked_store(&mut self, addr: Address, v: DataValue) -> Result<(), MemoryError> {
         let store_size = v.ty().bytes() as usize;
+        let addr_start = addr.offset as usize;
+        let addr_end = addr_start + store_size;
 
         let dst = match addr.region {
             AddressRegion::Stack => {
-                let addr_start = addr.offset as usize;
-                let addr_end = addr_start + store_size;
                 if addr_end > self.stack.len() {
                     return Err(MemoryError::OutOfBoundsStore { addr, store_size });
                 }
 
                 &mut self.stack[addr_start..addr_end]
             }
+            AddressRegion::Heap => {
+                let heap_mem = match self.heaps.get_mut(addr.entry as usize) {
+                    Some(mem) if addr_end <= mem.len() => mem,
+                    _ => return Err(MemoryError::OutOfBoundsStore { addr, store_size }),
+                };
+
+                &mut heap_mem[addr_start..addr_end]
+            }
             _ => unimplemented!(),
         };
 
         Ok(v.write_to_slice(dst))
     }
+
+    /// Non-Recursively resolves a global value until its address is found
+    fn resolve_global_value(&self, gv: GlobalValue) -> Result<DataValue, MemoryError> {
+        // Resolving a Global Value is a "pointer" chasing operation that lends itself to
+        // using a recursive solution. However, resolving this in a recursive manner
+        // is a bad idea because its very easy to add a bunch of global values and
+        // blow up the call stack.
+        //
+        // Adding to the challenges of this, is that the operations possible with GlobalValues
+        // mean that we cannot use a simple loop to resolve each global value, we must keep
+        // a pending list of operations.
+
+        // These are the possible actions that we can perform
+        #[derive(Debug)]
+        enum ResolveAction {
+            Resolve(GlobalValue),
+            /// Perform an add on the current address
+            Add(DataValue),
+            /// Load From the current address and replace it with the loaded value
+            Load {
+                /// Offset added to the base pointer before doing the load.
+                offset: i32,
+
+                /// Type of the loaded value.
+                global_type: Type,
+            },
+        }
+
+        let func = self.get_current_function();
+
+        // We start with a sentinel value that will fail if we try to load / add to it
+        // without resolving the base GV First.
+        let mut current_val = DataValue::B(false);
+        let mut action_stack = vec![ResolveAction::Resolve(gv)];
+
+        loop {
+            match action_stack.pop() {
+                Some(ResolveAction::Resolve(gv)) => match func.global_values[gv] {
+                    GlobalValueData::VMContext => {
+                        // Fetch the VMContext value from the values of the first block in the function
+                        let index = func
+                            .signature
+                            .params
+                            .iter()
+                            .enumerate()
+                            .find(|(_, p)| p.purpose == ArgumentPurpose::VMContext)
+                            .map(|(i, _)| i)
+                            // This should be validated by the verifier
+                            .expect("No VMCtx argument was found, but one is referenced");
+
+                        let first_block =
+                            func.layout.blocks().next().expect("to have a first block");
+                        let vmctx_value = func.dfg.block_params(first_block)[index];
+                        current_val = self.current_frame().get(vmctx_value).clone();
+                    }
+                    GlobalValueData::Load {
+                        base,
+                        offset,
+                        global_type,
+                        ..
+                    } => {
+                        action_stack.push(ResolveAction::Load {
+                            offset: offset.into(),
+                            global_type,
+                        });
+                        action_stack.push(ResolveAction::Resolve(base));
+                    }
+                    GlobalValueData::IAddImm {
+                        base,
+                        offset,
+                        global_type,
+                    } => {
+                        let offset: i64 = offset.into();
+                        let dv = DataValue::int(offset as i128, global_type)
+                            .map_err(|_| MemoryError::InvalidAddressType(global_type))?;
+                        action_stack.push(ResolveAction::Add(dv));
+                        action_stack.push(ResolveAction::Resolve(base));
+                    }
+                    GlobalValueData::Symbol { .. } => unimplemented!(),
+                },
+                Some(ResolveAction::Add(dv)) => {
+                    current_val = current_val
+                        .add(dv.clone())
+                        .map_err(|_| MemoryError::InvalidAddress(dv))?;
+                }
+                Some(ResolveAction::Load {
+                    offset,
+                    global_type,
+                }) => {
+                    let mut addr = Address::try_from(current_val)?;
+                    // We can forego bounds checking here since its performed in `checked_load`
+                    addr.offset += offset as u64;
+                    current_val = self.checked_load(addr, global_type)?;
+                }
+
+                // We are done resolving this, return the current value
+                None => return Ok(current_val),
+            }
+        }
+    }
+
+    fn validate_address(&self, addr: &Address) -> Result<(), MemoryError> {
+        match addr.region {
+            AddressRegion::Stack => {
+                let stack_len = self.stack.len() as u64;
+
+                if addr.offset > stack_len {
+                    return Err(MemoryError::InvalidEntry {
+                        entry: addr.entry,
+                        max: self.heaps.len() as u64,
+                    });
+                }
+            }
+            AddressRegion::Heap => {
+                let heap_len = self
+                    .heaps
+                    .get(addr.entry as usize)
+                    .ok_or_else(|| MemoryError::InvalidEntry {
+                        entry: addr.entry,
+                        max: self.heaps.len() as u64,
+                    })
+                    .map(|heap| heap.len() as u64)?;
+
+                if addr.offset > heap_len {
+                    return Err(MemoryError::InvalidOffset {
+                        offset: addr.offset,
+                        max: heap_len,
+                    });
+                }
+            }
+            _ => unimplemented!(),
+        }
+
+        Ok(())
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::step::CraneliftTrap;
+    use cranelift_codegen::ir::types::I64;
     use cranelift_codegen::ir::TrapCode;
     use cranelift_reader::parse_functions;
 
@@ -720,4 +965,51 @@ mod tests {
 
         assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds));
     }
+
+    /// Most heap tests are in .clif files using the filetest machinery. However, this is a sanity
+    /// check that the heap mechanism works without the rest of the filetest infrastructure
+    #[test]
+    fn heap_sanity_test() {
+        let code = "
+        function %heap_load_store(i64 vmctx) -> b1 {
+            gv0 = vmctx
+            gv1 = load.i64 notrap aligned gv0+0
+            ; gv2/3 do nothing, but makes sure we understand the iadd_imm mechanism
+            gv2 = iadd_imm.i64 gv1, 1
+            gv3 = iadd_imm.i64 gv2, -1
+            heap0 = static gv3, min 0x1000, bound 0x1_0000_0000, offset_guard 0, index_type i64
+
+        block0(v0: i64):
+            v1 = iconst.i64 0
+            v2 = iconst.i64 123
+            v3 = heap_addr.i64 heap0, v1, 8
+            store.i64 v2, v3
+            v4 = load.i64 v3
+            v5 = icmp eq v2, v4
+            return v5
+        }";
+
+        let func = parse_functions(code).unwrap().into_iter().next().unwrap();
+        let mut env = FunctionStore::default();
+        env.add(func.name.to_string(), &func);
+        let mut state = InterpreterState::default().with_function_store(env);
+
+        let heap0 = state.register_heap(HeapInit::Zeroed(0x1000));
+        let base_addr = state.get_heap_address(I64, heap0, 0).unwrap();
+
+        // Build a vmctx struct by writing the base pointer at index 0
+        let mut vmctx_struct = vec![0u8; 8];
+        base_addr.write_to_slice(&mut vmctx_struct[..]);
+
+        // This is our vmctx "heap"
+        let vmctx = state.register_heap(HeapInit::FromBacking(vmctx_struct));
+        let vmctx_addr = state.get_heap_address(I64, vmctx, 0).unwrap();
+
+        let result = Interpreter::new(state)
+            .call_by_name("%heap_load_store", &[vmctx_addr])
+            .unwrap()
+            .unwrap_return();
+
+        assert_eq!(result, vec![DataValue::B(true)])
+    }
 }
diff --git a/cranelift/interpreter/src/state.rs b/cranelift/interpreter/src/state.rs
index 22381c893..efc184272 100644
--- a/cranelift/interpreter/src/state.rs
+++ b/cranelift/interpreter/src/state.rs
@@ -3,7 +3,7 @@
 use crate::address::{Address, AddressSize};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
-use cranelift_codegen::ir::{FuncRef, Function, StackSlot, Type, Value};
+use cranelift_codegen::ir::{FuncRef, Function, GlobalValue, Heap, StackSlot, Type, Value};
 use cranelift_entity::PrimaryMap;
 use smallvec::SmallVec;
 use thiserror::Error;
@@ -67,13 +67,25 @@ pub trait State<'a, V> {
         offset: u64,
     ) -> Result<Address, MemoryError>;
     /// Computes a heap address
-    fn heap_address(&self, size: AddressSize, offset: u64) -> Result<Address, MemoryError>;
+    fn heap_address(
+        &self,
+        size: AddressSize,
+        heap: Heap,
+        offset: u64,
+    ) -> Result<Address, MemoryError>;
     /// Retrieve a value `V` from memory at the given `address`, checking if it belongs either to the
     /// stack or to one of the heaps; the number of bytes loaded corresponds to the specified [Type].
     fn checked_load(&self, address: Address, ty: Type) -> Result<V, MemoryError>;
     /// Store a value `V` into memory at the given `address`, checking if it belongs either to the
     /// stack or to one of the heaps; the number of bytes stored corresponds to the specified [Type].
     fn checked_store(&mut self, address: Address, v: V) -> Result<(), MemoryError>;
+
+    /// Given a global value, compute the final value for that global value, applying all operations
+    /// in intermediate global values.
+    fn resolve_global_value(&self, gv: GlobalValue) -> Result<V, MemoryError>;
+
+    /// Checks if an address is valid and within a known region of memory
+    fn validate_address(&self, address: &Address) -> Result<(), MemoryError>;
 }
 
 #[derive(Error, Debug)]
@@ -151,7 +163,12 @@ where
         unimplemented!()
     }
 
-    fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result<Address, MemoryError> {
+    fn heap_address(
+        &self,
+        _size: AddressSize,
+        _heap: Heap,
+        _offset: u64,
+    ) -> Result<Address, MemoryError> {
         unimplemented!()
     }
 
@@ -162,4 +179,12 @@ where
     fn checked_store(&mut self, _addr: Address, _v: V) -> Result<(), MemoryError> {
         unimplemented!()
     }
+
+    fn resolve_global_value(&self, _gv: GlobalValue) -> Result<V, MemoryError> {
+        unimplemented!()
+    }
+
+    fn validate_address(&self, _addr: &Address) -> Result<(), MemoryError> {
+        unimplemented!()
+    }
 }
diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs
index 91bbcf46d..b43b86a92 100644
--- a/cranelift/interpreter/src/step.rs
+++ b/cranelift/interpreter/src/step.rs
@@ -7,11 +7,12 @@ use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
 use cranelift_codegen::ir::{
-    types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Value as ValueRef,
+    types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Type, Value as ValueRef,
 };
 use log::trace;
 use smallvec::{smallvec, SmallVec};
 use std::convert::{TryFrom, TryInto};
+use std::fmt::Debug;
 use std::ops::RangeFrom;
 use thiserror::Error;
 
@@ -135,11 +136,11 @@ where
         Err(e) => ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e))),
     };
 
-    let calculate_addr = |imm: V, args: SmallVec<[V; 1]>| -> ValueResult<u64> {
-        let imm = imm.convert(ValueConversionKind::ZeroExtend(ctrl_ty))?;
+    let calculate_addr = |addr_ty: Type, imm: V, args: SmallVec<[V; 1]>| -> ValueResult<u64> {
+        let imm = imm.convert(ValueConversionKind::ZeroExtend(addr_ty))?;
         let args = args
             .into_iter()
-            .map(|v| v.convert(ValueConversionKind::ZeroExtend(ctrl_ty)))
+            .map(|v| v.convert(ValueConversionKind::ZeroExtend(addr_ty)))
             .collect::<ValueResult<SmallVec<[V; 1]>>>()?;
 
         Ok(sum(imm, args)? as u64)
@@ -315,7 +316,7 @@ where
                 _ => unreachable!(),
             };
 
-            let addr_value = calculate_addr(imm(), args()?)?;
+            let addr_value = calculate_addr(types::I64, imm(), args()?)?;
             let loaded = assign_or_memtrap(
                 Address::try_from(addr_value).and_then(|addr| state.checked_load(addr, load_ty)),
             );
@@ -338,7 +339,7 @@ where
                 _ => unreachable!(),
             };
 
-            let addr_value = calculate_addr(imm(), args_range(1..)?)?;
+            let addr_value = calculate_addr(types::I64, imm(), args_range(1..)?)?;
             let reduced = if let Some(c) = kind {
                 arg(0)?.convert(c)?
             } else {
@@ -383,7 +384,21 @@ where
         Opcode::GlobalValue => unimplemented!("GlobalValue"),
         Opcode::SymbolValue => unimplemented!("SymbolValue"),
         Opcode::TlsValue => unimplemented!("TlsValue"),
-        Opcode::HeapAddr => unimplemented!("HeapAddr"),
+        Opcode::HeapAddr => {
+            if let InstructionData::HeapAddr { heap, .. } = inst {
+                let load_ty = inst_context.controlling_type().unwrap();
+                let offset = calculate_addr(ctrl_ty, imm(), args()?)? as u64;
+                assign_or_memtrap({
+                    AddressSize::try_from(load_ty).and_then(|addr_size| {
+                        let addr = state.heap_address(addr_size, heap, offset)?;
+                        let dv = DataValue::try_from(addr)?;
+                        Ok(dv.into())
+                    })
+                })
+            } else {
+                unreachable!()
+            }
+        }
         Opcode::GetPinnedReg => unimplemented!("GetPinnedReg"),
         Opcode::SetPinnedReg => unimplemented!("SetPinnedReg"),
         Opcode::TableAddr => unimplemented!("TableAddr"),
@@ -679,10 +694,10 @@ where
         Opcode::Fmul => binary(Value::mul, arg(0)?, arg(1)?)?,
         Opcode::Fdiv => binary(Value::div, arg(0)?, arg(1)?)?,
         Opcode::Sqrt => assign(Value::sqrt(arg(0)?)?),
-        Opcode::Fma => unimplemented!("Fma"),
-        Opcode::Fneg => binary(Value::sub, Value::float(0, ctrl_ty)?, arg(0)?)?,
-        Opcode::Fabs => unimplemented!("Fabs"),
-        Opcode::Fcopysign => unimplemented!("Fcopysign"),
+        Opcode::Fma => assign(Value::fma(arg(0)?, arg(1)?, arg(2)?)?),
+        Opcode::Fneg => assign(Value::neg(arg(0)?)?),
+        Opcode::Fabs => assign(Value::abs(arg(0)?)?),
+        Opcode::Fcopysign => binary(Value::copysign, arg(0)?, arg(1)?)?,
         Opcode::Fmin => choose(
             Value::is_nan(&arg(0)?)? || Value::lt(&arg(0)?, &arg(1)?)?,
             arg(0)?,
diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs
index a38d2aca4..9fe7baf53 100644
--- a/cranelift/interpreter/src/value.rs
+++ b/cranelift/interpreter/src/value.rs
@@ -51,6 +51,12 @@ pub trait Value: Clone + From<DataValue> {
     fn div(self, other: Self) -> ValueResult<Self>;
     fn rem(self, other: Self) -> ValueResult<Self>;
     fn sqrt(self) -> ValueResult<Self>;
+    fn fma(self, a: Self, b: Self) -> ValueResult<Self>;
+    fn abs(self) -> ValueResult<Self>;
+
+    // Float operations
+    fn neg(self) -> ValueResult<Self>;
+    fn copysign(self, sign: Self) -> ValueResult<Self>;
 
     // Saturating arithmetic.
     fn add_sat(self, other: Self) -> ValueResult<Self>;
@@ -468,6 +474,30 @@ impl Value for DataValue {
         unary_match!(sqrt(&self); [F32, F64]; [Ieee32, Ieee64])
     }
 
+    fn fma(self, b: Self, c: Self) -> ValueResult<Self> {
+        match (self, b, c) {
+            (DataValue::F32(a), DataValue::F32(b), DataValue::F32(c)) => {
+                Ok(DataValue::F32(a.mul_add(b, c)))
+            }
+            (DataValue::F64(a), DataValue::F64(b), DataValue::F64(c)) => {
+                Ok(DataValue::F64(a.mul_add(b, c)))
+            }
+            (a, _b, _c) => Err(ValueError::InvalidType(ValueTypeClass::Float, a.ty())),
+        }
+    }
+
+    fn abs(self) -> ValueResult<Self> {
+        unary_match!(abs(&self); [F32, F64])
+    }
+
+    fn neg(self) -> ValueResult<Self> {
+        unary_match!(neg(&self); [F32, F64])
+    }
+
+    fn copysign(self, sign: Self) -> ValueResult<Self> {
+        binary_match!(copysign(&self, &sign); [F32, F64])
+    }
+
     fn add_sat(self, other: Self) -> ValueResult<Self> {
         binary_match!(saturating_add(self, &other); [I8, I16, I32, I64, I128, U8, U16, U32, U64, U128])
     }
diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml
index e6c5b192c..91ee21908 100644
--- a/cranelift/isle/isle/Cargo.toml
+++ b/cranelift/isle/isle/Cargo.toml
@@ -6,7 +6,7 @@ license = "Apache-2.0 WITH LLVM-exception"
 name = "cranelift-isle"
 readme = "../README.md"
 repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/isle"
-version = "0.86.0"
+version = "0.87.0"
 
 [dependencies]
 log = { version = "0.4", optional = true }
diff --git a/cranelift/jit/Cargo.toml b/cranelift/jit/Cargo.toml
index 8936cf926..594bd784a 100644
--- a/cranelift/jit/Cargo.toml
+++ b/cranelift/jit/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-jit"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "A JIT library backed by Cranelift"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -10,10 +10,10 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-module = { path = "../module", version = "0.86.0" }
-cranelift-native = { path = "../native", version = "0.86.0" }
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false, features = ["std"] }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
+cranelift-module = { path = "../module", version = "0.87.0" }
+cranelift-native = { path = "../native", version = "0.87.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false, features = ["std"] }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
 anyhow = "1.0"
 region = "2.2.0"
 libc = { version = "0.2.42" }
@@ -34,9 +34,9 @@ selinux-fix = ['memmap2']
 default = []
 
 [dev-dependencies]
-cranelift = { path = "../umbrella", version = "0.86.0" }
-cranelift-frontend = { path = "../frontend", version = "0.86.0" }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
+cranelift = { path = "../umbrella", version = "0.87.0" }
+cranelift-frontend = { path = "../frontend", version = "0.87.0" }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/module/Cargo.toml b/cranelift/module/Cargo.toml
index 2b560eec4..20f150e47 100644
--- a/cranelift/module/Cargo.toml
+++ b/cranelift/module/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-module"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Support for linking functions and data with Cranelift"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -11,7 +11,7 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
 hashbrown = { version = "0.11", optional = true }
 anyhow = "1.0"
 
diff --git a/cranelift/native/Cargo.toml b/cranelift/native/Cargo.toml
index 0c613a895..d0038fc9c 100644
--- a/cranelift/native/Cargo.toml
+++ b/cranelift/native/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-native"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Support for targeting the host with Cranelift"
 documentation = "https://docs.rs/cranelift-native"
@@ -11,7 +11,7 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
 target-lexicon = "0.12"
 
 [target.'cfg(target_arch = "s390x")'.dependencies]
diff --git a/cranelift/object/Cargo.toml b/cranelift/object/Cargo.toml
index 8a492563d..7d0951acf 100644
--- a/cranelift/object/Cargo.toml
+++ b/cranelift/object/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-object"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Emit Cranelift output to native object files with `object`"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -10,16 +10,16 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-module = { path = "../module", version = "0.86.0" }
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false, features = ["std"] }
+cranelift-module = { path = "../module", version = "0.87.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false, features = ["std"] }
 object = { version = "0.28.0", default-features = false, features = ["write"] }
 target-lexicon = "0.12"
 anyhow = "1.0"
 log = { version = "0.4.6", default-features = false }
 
 [dev-dependencies]
-cranelift-frontend = { path = "../frontend", version = "0.86.0" }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
+cranelift-frontend = { path = "../frontend", version = "0.87.0" }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/preopt/Cargo.toml b/cranelift/preopt/Cargo.toml
index e2d9658e8..ea05b775d 100644
--- a/cranelift/preopt/Cargo.toml
+++ b/cranelift/preopt/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-preopt"
-version = "0.86.0"
+version = "0.87.0"
 description = "Support for optimizations in Cranelift"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-preopt"
@@ -12,7 +12,7 @@ keywords = ["optimize", "compile", "compiler", "jit"]
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
 # This is commented out because it doesn't build on Rust 1.25.0, which
 # cranelift currently supports.
 # rustc_apfloat = { version = "0.1.2", default-features = false }
diff --git a/cranelift/reader/Cargo.toml b/cranelift/reader/Cargo.toml
index 8a205363d..be876aa66 100644
--- a/cranelift/reader/Cargo.toml
+++ b/cranelift/reader/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift-reader"
-version = "0.86.0"
+version = "0.87.0"
 description = "Cranelift textual IR reader"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-reader"
@@ -10,7 +10,7 @@ readme = "README.md"
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0" }
 smallvec = "1.6.1"
 target-lexicon = "0.12"
 
diff --git a/cranelift/serde/Cargo.toml b/cranelift/serde/Cargo.toml
index 7994ca028..f51d42174 100644
--- a/cranelift/serde/Cargo.toml
+++ b/cranelift/serde/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-serde"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Serializer/Deserializer for Cranelift IR"
 repository = "https://github.com/bytecodealliance/wasmtime"
@@ -16,8 +16,8 @@ path = "src/clif-json.rs"
 [dependencies]
 clap = { version = "3.1.12", features = ["derive"] }
 serde_json = "1.0.26"
-cranelift-codegen = { path = "../codegen", version = "0.86.0", features = ["enable-serde"] }
-cranelift-reader = { path = "../reader", version = "0.86.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", features = ["enable-serde"] }
+cranelift-reader = { path = "../reader", version = "0.87.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/cranelift/umbrella/Cargo.toml b/cranelift/umbrella/Cargo.toml
index 7f3152139..a7b8f0106 100644
--- a/cranelift/umbrella/Cargo.toml
+++ b/cranelift/umbrella/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 authors = ["The Cranelift Project Developers"]
 name = "cranelift"
-version = "0.86.0"
+version = "0.87.0"
 description = "Umbrella for commonly-used cranelift crates"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift"
@@ -12,8 +12,8 @@ keywords = ["compile", "compiler", "jit"]
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
-cranelift-frontend = { path = "../frontend", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
+cranelift-frontend = { path = "../frontend", version = "0.87.0", default-features = false }
 
 [features]
 default = ["std"]
diff --git a/cranelift/wasm/Cargo.toml b/cranelift/wasm/Cargo.toml
index 59d15a311..443de0a9a 100644
--- a/cranelift/wasm/Cargo.toml
+++ b/cranelift/wasm/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cranelift-wasm"
-version = "0.86.0"
+version = "0.87.0"
 authors = ["The Cranelift Project Developers"]
 description = "Translator from WebAssembly to Cranelift IR"
 documentation = "https://docs.rs/cranelift-wasm"
@@ -13,10 +13,10 @@ edition = "2021"
 
 [dependencies]
 wasmparser = { version = "0.86.0", default-features = false }
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
-cranelift-entity = { path = "../entity", version = "0.86.0" }
-cranelift-frontend = { path = "../frontend", version = "0.86.0", default-features = false }
-wasmtime-types = { path = "../../crates/types", version = "0.39.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
+cranelift-entity = { path = "../entity", version = "0.87.0" }
+cranelift-frontend = { path = "../frontend", version = "0.87.0", default-features = false }
+wasmtime-types = { path = "../../crates/types", version = "0.40.0" }
 hashbrown = { version = "0.11", optional = true }
 itertools = "0.10.0"
 log = { version = "0.4.6", default-features = false }
@@ -26,7 +26,7 @@ smallvec = "1.6.1"
 [dev-dependencies]
 wat = "1.0.37"
 target-lexicon = "0.12"
-cranelift-codegen = { path = "../codegen", version = "0.86.0", default-features = false }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
 
 [features]
 default = ["std"]
diff --git a/crates/cache/Cargo.toml b/crates/cache/Cargo.toml
index fc129f6b1..efcb4ac27 100644
--- a/crates/cache/Cargo.toml
+++ b/crates/cache/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-cache"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Support for automatic module caching with Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/cli-flags/Cargo.toml b/crates/cli-flags/Cargo.toml
index 2c0be98ff..dbfbe4a8a 100644
--- a/crates/cli-flags/Cargo.toml
+++ b/crates/cli-flags/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-cli-flags"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Exposes common CLI flags used for running Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -14,7 +14,7 @@ clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] }
 file-per-thread-logger = "0.1.1"
 pretty_env_logger = "0.4.0"
 rayon = "1.5.0"
-wasmtime = { path = "../wasmtime", version = "0.39.0", default-features = false }
+wasmtime = { path = "../wasmtime", version = "0.40.0", default-features = false }
 
 [features]
 default = [
diff --git a/crates/component-macro/Cargo.toml b/crates/component-macro/Cargo.toml
index 4e924ec97..6fadf5e65 100644
--- a/crates/component-macro/Cargo.toml
+++ b/crates/component-macro/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-component-macro"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Macros for deriving component interface types from Rust types"
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/component-macro/src/lib.rs b/crates/component-macro/src/lib.rs
index b956c9e31..878c53f68 100644
--- a/crates/component-macro/src/lib.rs
+++ b/crates/component-macro/src/lib.rs
@@ -145,6 +145,54 @@ fn add_trait_bounds(generics: &syn::Generics, bound: syn::TypeParamBound) -> syn
     generics
 }
 
+#[derive(Debug, Copy, Clone)]
+enum DiscriminantSize {
+    Size1,
+    Size2,
+    Size4,
+}
+
+impl DiscriminantSize {
+    fn quote(self, discriminant: usize) -> TokenStream {
+        match self {
+            Self::Size1 => {
+                let discriminant = u8::try_from(discriminant).unwrap();
+                quote!(#discriminant)
+            }
+            Self::Size2 => {
+                let discriminant = u16::try_from(discriminant).unwrap();
+                quote!(#discriminant)
+            }
+            Self::Size4 => {
+                let discriminant = u32::try_from(discriminant).unwrap();
+                quote!(#discriminant)
+            }
+        }
+    }
+}
+
+impl From<DiscriminantSize> for u32 {
+    fn from(size: DiscriminantSize) -> u32 {
+        match size {
+            DiscriminantSize::Size1 => 1,
+            DiscriminantSize::Size2 => 2,
+            DiscriminantSize::Size4 => 4,
+        }
+    }
+}
+
+fn discriminant_size(case_count: usize) -> Option<DiscriminantSize> {
+    if case_count <= 0xFF {
+        Some(DiscriminantSize::Size1)
+    } else if case_count <= 0xFFFF {
+        Some(DiscriminantSize::Size2)
+    } else if case_count <= 0xFFFF_FFFF {
+        Some(DiscriminantSize::Size4)
+    } else {
+        None
+    }
+}
+
 struct VariantCase<'a> {
     attrs: &'a [syn::Attribute],
     ident: &'a syn::Ident,
@@ -157,6 +205,7 @@ trait Expander {
     fn expand_variant(
         &self,
         input: &DeriveInput,
+        discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
         style: VariantStyle,
     ) -> Result<TokenStream>;
@@ -217,6 +266,13 @@ fn expand_variant(
         ));
     }
 
+    let discriminant_size = discriminant_size(body.variants.len()).ok_or_else(|| {
+        Error::new(
+            input.ident.span(),
+            "`enum`s with more than 2^32 variants are not supported",
+        )
+    })?;
+
     let cases = body
         .variants
         .iter()
@@ -240,8 +296,13 @@ fn expand_variant(
                                 name.span(),
                                 format!(
                                     "`{}` component types can only be derived for Rust `enum`s \
-                                     containing variants with at most one unnamed field each",
-                                    style
+                                     containing variants with {}",
+                                    style,
+                                    match style {
+                                        VariantStyle::Variant => "at most one unnamed field each",
+                                        VariantStyle::Enum => "no fields",
+                                        VariantStyle::Union => "exactly one unnamed field each",
+                                    }
                                 ),
                             ))
                         }
@@ -251,7 +312,7 @@ fn expand_variant(
         )
         .collect::<Result<Vec<_>>>()?;
 
-    expander.expand_variant(input, &cases, style)
+    expander.expand_variant(input, discriminant_size, &cases, style)
 }
 
 #[proc_macro_derive(Lift, attributes(component))]
@@ -321,6 +382,7 @@ impl Expander for LiftExpander {
     fn expand_variant(
         &self,
         input: &DeriveInput,
+        discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
         _style: VariantStyle,
     ) -> Result<TokenStream> {
@@ -330,31 +392,26 @@ impl Expander for LiftExpander {
         let mut loads = TokenStream::new();
 
         for (index, VariantCase { ident, ty, .. }) in cases.iter().enumerate() {
-            let index_u8 = u8::try_from(index).map_err(|_| {
-                Error::new(
-                    input.ident.span(),
-                    "`enum`s with more than 256 variants not yet supported",
-                )
-            })?;
+            let index_u32 = u32::try_from(index).unwrap();
 
-            let index_i32 = index_u8 as i32;
+            let index_quoted = discriminant_size.quote(index);
 
             if let Some(ty) = ty {
                 lifts.extend(
-                    quote!(#index_i32 => Self::#ident(<#ty as wasmtime::component::Lift>::lift(
+                    quote!(#index_u32 => Self::#ident(<#ty as wasmtime::component::Lift>::lift(
                         store, options, unsafe { &src.payload.#ident }
                     )?),),
                 );
 
                 loads.extend(
-                    quote!(#index_u8 => Self::#ident(<#ty as wasmtime::component::Lift>::load(
+                    quote!(#index_quoted => Self::#ident(<#ty as wasmtime::component::Lift>::load(
                         memory, &payload[..<#ty as wasmtime::component::ComponentType>::SIZE32]
                     )?),),
                 );
             } else {
-                lifts.extend(quote!(#index_i32 => Self::#ident,));
+                lifts.extend(quote!(#index_u32 => Self::#ident,));
 
-                loads.extend(quote!(#index_u8 => Self::#ident,));
+                loads.extend(quote!(#index_quoted => Self::#ident,));
             }
         }
 
@@ -362,6 +419,14 @@ impl Expander for LiftExpander {
         let generics = add_trait_bounds(&input.generics, parse_quote!(wasmtime::component::Lift));
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
+        let from_bytes = match discriminant_size {
+            DiscriminantSize::Size1 => quote!(bytes[0]),
+            DiscriminantSize::Size2 => quote!(u16::from_le_bytes(bytes[0..2].try_into()?)),
+            DiscriminantSize::Size4 => quote!(u32::from_le_bytes(bytes[0..4].try_into()?)),
+        };
+
+        let payload_offset = u32::from(discriminant_size) as usize;
+
         let expanded = quote! {
             unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause {
                 #[inline]
@@ -370,7 +435,7 @@ impl Expander for LiftExpander {
                     options: &#internal::Options,
                     src: &Self::Lower,
                 ) -> #internal::anyhow::Result<Self> {
-                    Ok(match src.tag.get_i32() {
+                    Ok(match src.tag.get_u32() {
                         #lifts
                         discrim => #internal::anyhow::bail!("unexpected discriminant: {}", discrim),
                     })
@@ -380,8 +445,8 @@ impl Expander for LiftExpander {
                 fn load(memory: &#internal::Memory, bytes: &[u8]) -> #internal::anyhow::Result<Self> {
                     let align = <Self as wasmtime::component::ComponentType>::ALIGN32;
                     debug_assert!((bytes.as_ptr() as usize) % (align as usize) == 0);
-                    let discrim = bytes[0];
-                    let payload = &bytes[#internal::align_to(1, align)..];
+                    let discrim = #from_bytes;
+                    let payload = &bytes[#internal::align_to(#payload_offset, align)..];
                     Ok(match discrim {
                         #loads
                         discrim => #internal::anyhow::bail!("unexpected discriminant: {}", discrim),
@@ -456,6 +521,7 @@ impl Expander for LowerExpander {
     fn expand_variant(
         &self,
         input: &DeriveInput,
+        discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
         _style: VariantStyle,
     ) -> Result<TokenStream> {
@@ -465,14 +531,9 @@ impl Expander for LowerExpander {
         let mut stores = TokenStream::new();
 
         for (index, VariantCase { ident, ty, .. }) in cases.iter().enumerate() {
-            let index_u8 = u8::try_from(index).map_err(|_| {
-                Error::new(
-                    input.ident.span(),
-                    "`enum`s with more than 256 variants not yet supported",
-                )
-            })?;
+            let index_u32 = u32::try_from(index).unwrap();
 
-            let index_i32 = index_u8 as i32;
+            let index_quoted = discriminant_size.quote(index);
 
             let pattern;
             let lower;
@@ -492,12 +553,14 @@ impl Expander for LowerExpander {
             }
 
             lowers.extend(quote!(#pattern => {
-                #internal::map_maybe_uninit!(dst.tag).write(wasmtime::ValRaw::i32(#index_i32));
+                #internal::map_maybe_uninit!(dst.tag).write(wasmtime::ValRaw::i32(#index_u32 as i32));
                 #lower
             }));
 
+            let discriminant_size = u32::from(discriminant_size) as usize;
+
             stores.extend(quote!(#pattern => {
-                memory.get::<1>(offset)[0] = #index_u8;
+                *memory.get::<#discriminant_size>(offset) = #index_quoted.to_le_bytes();
                 #store
             }));
         }
@@ -668,6 +731,7 @@ impl Expander for ComponentTypeExpander {
     fn expand_variant(
         &self,
         input: &DeriveInput,
+        discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
         style: VariantStyle,
     ) -> Result<TokenStream> {
@@ -766,6 +830,7 @@ impl Expander for ComponentTypeExpander {
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
         let lower = format_ident!("Lower{}", name);
         let lower_payload = format_ident!("LowerPayload{}", name);
+        let discriminant_size = u32::from(discriminant_size);
 
         // You may wonder why we make the types of all the fields of the #lower struct and #lower_payload union
         // generic.  This is to work around a [normalization bug in
@@ -806,11 +871,11 @@ impl Expander for ComponentTypeExpander {
                 const SIZE32: usize = {
                     let mut size = 0;
                     #sizes
-                    #internal::align_to(1, Self::ALIGN32) + size
+                    #internal::align_to(#discriminant_size as usize, Self::ALIGN32) + size
                 };
 
                 const ALIGN32: u32 = {
-                    let mut align = 1;
+                    let mut align = #discriminant_size;
                     #alignments
                     align
                 };
diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml
index e8ec94f37..fe167d8a4 100644
--- a/crates/cranelift/Cargo.toml
+++ b/crates/cranelift/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-cranelift"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Integration between Cranelift and Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -13,12 +13,12 @@ edition = "2021"
 [dependencies]
 anyhow = "1.0"
 log = "0.4"
-wasmtime-environ = { path = "../environ", version = "=0.39.0" }
-cranelift-wasm = { path = "../../cranelift/wasm", version = "0.86.0" }
-cranelift-codegen = { path = "../../cranelift/codegen", version = "0.86.0" }
-cranelift-frontend = { path = "../../cranelift/frontend", version = "0.86.0" }
-cranelift-entity = { path = "../../cranelift/entity", version = "0.86.0" }
-cranelift-native = { path = "../../cranelift/native", version = "0.86.0" }
+wasmtime-environ = { path = "../environ", version = "=0.40.0" }
+cranelift-wasm = { path = "../../cranelift/wasm", version = "0.87.0" }
+cranelift-codegen = { path = "../../cranelift/codegen", version = "0.87.0" }
+cranelift-frontend = { path = "../../cranelift/frontend", version = "0.87.0" }
+cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
+cranelift-native = { path = "../../cranelift/native", version = "0.87.0" }
 wasmparser = "0.86.0"
 target-lexicon = "0.12"
 gimli = { version = "0.26.0", default-features = false, features = ['read', 'std'] }
diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml
index dac7e4c0a..7572476a9 100644
--- a/crates/environ/Cargo.toml
+++ b/crates/environ/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-environ"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Standalone environment support for WebAsssembly code in Cranelift"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -12,8 +12,8 @@ edition = "2021"
 
 [dependencies]
 anyhow = "1.0"
-cranelift-entity = { path = "../../cranelift/entity", version = "0.86.0" }
-wasmtime-types = { path = "../types", version = "0.39.0" }
+cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
+wasmtime-types = { path = "../types", version = "0.40.0" }
 wasmparser = "0.86.0"
 indexmap = { version = "1.0.2", features = ["serde-1"] }
 thiserror = "1.0.4"
diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs
index d75a03857..b84bd3beb 100644
--- a/crates/environ/src/component/info.rs
+++ b/crates/environ/src/component/info.rs
@@ -393,6 +393,9 @@ pub enum Export {
     /// The module index here indexes a module recorded with
     /// `GlobalInitializer::SaveModule` above.
     Module(RuntimeModuleIndex),
+    /// A nested instance is being exported which has recursively defined
+    /// `Export` items.
+    Instance(IndexMap<String, Export>),
 }
 
 /// Canonical ABI options associated with a lifted or lowered function.
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index 33ff141ab..a9b42f016 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -1,5 +1,7 @@
 use crate::component::*;
-use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables};
+use crate::{
+    EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, Tunables,
+};
 use anyhow::{bail, Result};
 use std::collections::HashMap;
 use std::mem;
@@ -141,6 +143,9 @@ struct Translation<'data> {
     /// Type information from wasmparser about this component, available after
     /// the component has been completely translated.
     types: Option<wasmparser::types::Types>,
+
+    /// The types of all core wasm functions defined within this component.
+    funcs: PrimaryMap<FuncIndex, SignatureIndex>,
 }
 
 #[allow(missing_docs)]
@@ -304,7 +309,7 @@ impl<'a, 'data> Translator<'a, 'data> {
         // Wasmtime to process at runtime as well (e.g. no string lookups as
         // most everything is done through indices instead).
         let component = inline::run(
-            &mut self.types,
+            &self.types,
             &self.result,
             &self.static_modules,
             &self.static_components,
@@ -338,9 +343,34 @@ impl<'a, 'data> Translator<'a, 'data> {
             }
 
             Payload::End(offset) => {
+                let types = self.validator.end(offset)?;
+
                 // Record type information for this component now that we'll
                 // have it from wasmparser.
-                self.result.types = Some(self.validator.end(offset)?);
+                //
+                // Note that this uses type information from `wasmparser` to
+                // lookup signature of all core wasm functions in this
+                // component.  This avoids us having to reimplement the
+                // translate-interface-types-to-the-canonical-abi logic. The
+                // type of the function is then intern'd to get a
+                // `SignatureIndex` which is later used at runtime for a
+                // `VMSharedSignatureIndex`.
+                for init in self.result.initializers.iter() {
+                    match init {
+                        LocalInitializer::Lower(..) | LocalInitializer::AliasExportFunc(..) => {}
+                        _ => continue,
+                    }
+                    let idx = self.result.funcs.next_key();
+                    let lowered_function_type = types
+                        .function_at(idx.as_u32())
+                        .expect("should be in-bounds");
+                    let ty = self
+                        .types
+                        .module_types_builder()
+                        .wasm_func_type(lowered_function_type.clone().try_into()?);
+                    self.result.funcs.push(ty);
+                }
+                self.result.types = Some(types);
 
                 // When leaving a module be sure to pop the types scope to
                 // ensure that when we go back to the previous module outer
diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs
index 75f2334bf..290b22e13 100644
--- a/crates/environ/src/component/translate/inline.rs
+++ b/crates/environ/src/component/translate/inline.rs
@@ -50,7 +50,7 @@ use crate::{ModuleTranslation, PrimaryMap, SignatureIndex};
 use indexmap::IndexMap;
 
 pub(super) fn run(
-    types: &mut ComponentTypesBuilder,
+    types: &ComponentTypesBuilder,
     result: &Translation<'_>,
     nested_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
     nested_components: &PrimaryMap<StaticComponentIndex, Translation<'_>>,
@@ -82,23 +82,7 @@ pub(super) fn run(
         };
         let index = inliner.result.import_types.push((name.to_string(), ty));
         let path = ImportPath::root(index);
-        args.insert(
-            name,
-            match ty {
-                TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(path, ty)),
-                TypeDef::ComponentInstance(ty) => {
-                    ComponentItemDef::Instance(ComponentInstanceDef::Import(path, ty))
-                }
-                TypeDef::ComponentFunc(_ty) => {
-                    ComponentItemDef::Func(ComponentFuncDef::Import(path))
-                }
-                // FIXME(#4283) should commit one way or another to how this
-                // should be treated.
-                TypeDef::Component(_ty) => bail!("root-level component imports are not supported"),
-                TypeDef::Interface(_ty) => unimplemented!("import of a type"),
-                TypeDef::CoreFunc(_ty) => unreachable!(),
-            },
-        );
+        args.insert(name, ComponentItemDef::from_import(path, ty)?);
     }
 
     // This will run the inliner to completion after being seeded with the
@@ -116,58 +100,18 @@ pub(super) fn run(
     let exports = inliner.run(&mut frames)?;
     assert!(frames.is_empty());
 
+    let mut export_map = Default::default();
     for (name, def) in exports {
-        let export = match def {
-            // Exported modules are currently saved in a `PrimaryMap`, at
-            // runtime, so an index (`RuntimeModuleIndex`) is assigned here and
-            // then an initializer is recorded about where the module comes
-            // from.
-            ComponentItemDef::Module(module) => {
-                let index = RuntimeModuleIndex::from_u32(inliner.result.num_runtime_modules);
-                inliner.result.num_runtime_modules += 1;
-                let init = match module {
-                    ModuleDef::Static(idx) => GlobalInitializer::SaveStaticModule(idx),
-                    ModuleDef::Import(path, _) => {
-                        GlobalInitializer::SaveModuleImport(inliner.runtime_import(&path))
-                    }
-                };
-                inliner.result.initializers.push(init);
-                Export::Module(index)
-            }
-
-            // Currently only exported functions through liftings are supported
-            // which simply record the various lifting options here which get
-            // processed at runtime.
-            ComponentItemDef::Func(func) => match func {
-                ComponentFuncDef::Lifted { ty, func, options } => {
-                    Export::LiftedFunction { ty, func, options }
-                }
-                ComponentFuncDef::Import(_) => {
-                    bail!("component export `{name}` is a reexport of an imported function which is not implemented")
-                }
-            },
-
-            ComponentItemDef::Instance(_) => unimplemented!("exporting an instance to the host"),
-
-            // FIXME(#4283) should make an official decision on whether this is
-            // the final treatment of this or not.
-            ComponentItemDef::Component(_) => {
-                bail!("exporting a component from the root component is not supported")
-            }
-        };
-
-        inliner.result.exports.insert(name.to_string(), export);
+        inliner.record_export(name, def, &mut export_map)?;
     }
+    inliner.result.exports = export_map;
 
     Ok(inliner.result)
 }
 
 struct Inliner<'a> {
     /// Global type information for the entire component.
-    ///
-    /// Note that the mutability is used here to register a `SignatureIndex` for
-    /// the wasm function signature of lowered imports.
-    types: &'a mut ComponentTypesBuilder,
+    types: &'a ComponentTypesBuilder,
 
     /// The list of static modules that were found during initial translation of
     /// the component.
@@ -445,24 +389,7 @@ impl<'a> Inliner<'a> {
             //
             // NB: at this time only lowered imported functions are supported.
             Lower(func, options) => {
-                // Use the type information from `wasmparser` to lookup the core
-                // wasm function signature of the lowered function. This avoids
-                // us having to reimplement the
-                // translate-interface-types-to-the-canonical-abi logic. The
-                // type of the function is then intern'd to get a
-                // `SignatureIndex` which is later used at runtime for a
-                // `VMSharedSignatureIndex`.
-                let lowered_function_type = frame
-                    .translation
-                    .types
-                    .as_ref()
-                    .unwrap()
-                    .function_at(frame.funcs.next_key().as_u32())
-                    .expect("should be in-bounds");
-                let canonical_abi = self
-                    .types
-                    .module_types_builder()
-                    .wasm_func_type(lowered_function_type.clone().try_into()?);
+                let canonical_abi = frame.translation.funcs[frame.funcs.next_key()];
 
                 let options_lower = self.canonical_options(frame, options);
                 let func = match &frame.component_funcs[*func] {
@@ -937,6 +864,89 @@ impl<'a> Inliner<'a> {
             post_return,
         }
     }
+
+    fn record_export(
+        &mut self,
+        name: &str,
+        def: ComponentItemDef<'a>,
+        map: &mut IndexMap<String, Export>,
+    ) -> Result<()> {
+        let export = match def {
+            // Exported modules are currently saved in a `PrimaryMap`, at
+            // runtime, so an index (`RuntimeModuleIndex`) is assigned here and
+            // then an initializer is recorded about where the module comes
+            // from.
+            ComponentItemDef::Module(module) => {
+                let index = RuntimeModuleIndex::from_u32(self.result.num_runtime_modules);
+                self.result.num_runtime_modules += 1;
+                let init = match module {
+                    ModuleDef::Static(idx) => GlobalInitializer::SaveStaticModule(idx),
+                    ModuleDef::Import(path, _) => {
+                        GlobalInitializer::SaveModuleImport(self.runtime_import(&path))
+                    }
+                };
+                self.result.initializers.push(init);
+                Export::Module(index)
+            }
+
+            ComponentItemDef::Func(func) => match func {
+                // If this is a lifted function from something lowered in this
+                // component then the configured options are plumbed through
+                // here.
+                ComponentFuncDef::Lifted { ty, func, options } => {
+                    Export::LiftedFunction { ty, func, options }
+                }
+
+                // Currently reexported functions from an import are not
+                // supported. Being able to actually call these functions is
+                // somewhat tricky and needs something like temporary scratch
+                // space that isn't implemented.
+                ComponentFuncDef::Import(_) => {
+                    bail!("component export `{name}` is a reexport of an imported function which is not implemented")
+                }
+            },
+
+            ComponentItemDef::Instance(instance) => {
+                let mut result = IndexMap::new();
+                match instance {
+                    // If this instance is one that was originally imported by
+                    // the component itself then the imports are translated here
+                    // by converting to a `ComponentItemDef` and then
+                    // recursively recording the export as a reexport.
+                    //
+                    // 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() {
+                            let mut path = path.clone();
+                            path.path.push(name);
+                            let def = ComponentItemDef::from_import(path, *ty)?;
+                            self.record_export(name, def, &mut result)?;
+                        }
+                    }
+
+                    // An exported instance which is itself a bag of items is
+                    // translated recursively here to our `result` map which is
+                    // the bag of items we're exporting.
+                    ComponentInstanceDef::Items(map) => {
+                        for (name, def) in map {
+                            self.record_export(name, def, &mut result)?;
+                        }
+                    }
+                }
+                Export::Instance(result)
+            }
+
+            // FIXME(#4283) should make an official decision on whether this is
+            // the final treatment of this or not.
+            ComponentItemDef::Component(_) => {
+                bail!("exporting a component from the root component is not supported")
+            }
+        };
+
+        map.insert(name.to_string(), export);
+        Ok(())
+    }
 }
 
 impl<'a> InlinerFrame<'a> {
@@ -1004,3 +1014,21 @@ impl<'a> ImportPath<'a> {
         }
     }
 }
+
+impl<'a> ComponentItemDef<'a> {
+    fn from_import(path: ImportPath<'a>, ty: TypeDef) -> Result<ComponentItemDef<'a>> {
+        let item = match ty {
+            TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(path, ty)),
+            TypeDef::ComponentInstance(ty) => {
+                ComponentItemDef::Instance(ComponentInstanceDef::Import(path, ty))
+            }
+            TypeDef::ComponentFunc(_ty) => ComponentItemDef::Func(ComponentFuncDef::Import(path)),
+            // FIXME(#4283) should commit one way or another to how this
+            // should be treated.
+            TypeDef::Component(_ty) => bail!("root-level component imports are not supported"),
+            TypeDef::Interface(_ty) => unimplemented!("import of a type"),
+            TypeDef::CoreFunc(_ty) => unreachable!(),
+        };
+        Ok(item)
+    }
+}
diff --git a/crates/fiber/Cargo.toml b/crates/fiber/Cargo.toml
index 77a750ed9..f224489cf 100644
--- a/crates/fiber/Cargo.toml
+++ b/crates/fiber/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-fiber"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Fiber support for Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/fiber/build.rs b/crates/fiber/build.rs
index 9e7b8b5a3..6bfc5c4b9 100644
--- a/crates/fiber/build.rs
+++ b/crates/fiber/build.rs
@@ -5,13 +5,16 @@ fn main() {
     let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
     let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
     if os == "windows" {
+        println!("cargo:rerun-if-changed=src/windows.c");
         build.file("src/windows.c");
     } else if arch == "s390x" {
+        println!("cargo:rerun-if-changed=src/unix/s390x.S");
         build.file("src/unix/s390x.S");
     } else {
         // assume that this is included via inline assembly in the crate itself,
         // and the crate will otherwise have a `compile_error!` for unsupported
         // platforms.
+        println!("cargo:rerun-if-changed=build.rs");
         return;
     }
     build.define(&format!("CFG_TARGET_OS_{}", os), None);
diff --git a/crates/jit-debug/Cargo.toml b/crates/jit-debug/Cargo.toml
index 0af9aa5c2..a28d108a5 100644
--- a/crates/jit-debug/Cargo.toml
+++ b/crates/jit-debug/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-jit-debug"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "JIT debug interfaces support for Wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml
index 11372ae2d..c35896e53 100644
--- a/crates/jit/Cargo.toml
+++ b/crates/jit/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-jit"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "JIT-style execution for WebAsssembly code in Cranelift"
 documentation = "https://docs.rs/wasmtime-jit"
@@ -11,9 +11,9 @@ repository = "https://github.com/bytecodealliance/wasmtime"
 edition = "2021"
 
 [dependencies]
-wasmtime-environ = { path = "../environ", version = "=0.39.0" }
-wasmtime-jit-debug = { path = "../jit-debug", version = "=0.39.0", features = ["perf_jitdump"], optional = true }
-wasmtime-runtime = { path = "../runtime", version = "=0.39.0" }
+wasmtime-environ = { path = "../environ", version = "=0.40.0" }
+wasmtime-jit-debug = { path = "../jit-debug", version = "=0.40.0", features = ["perf_jitdump"], optional = true }
+wasmtime-runtime = { path = "../runtime", version = "=0.40.0" }
 region = "2.2.0"
 thiserror = "1.0.4"
 target-lexicon = { version = "0.12.0", default-features = false }
diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml
index dd664d7d2..fc5886eef 100644
--- a/crates/runtime/Cargo.toml
+++ b/crates/runtime/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-runtime"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Runtime library support for Wasmtime"
 documentation = "https://docs.rs/wasmtime-runtime"
@@ -11,9 +11,9 @@ repository = "https://github.com/bytecodealliance/wasmtime"
 edition = "2021"
 
 [dependencies]
-wasmtime-environ = { path = "../environ", version = "=0.39.0" }
-wasmtime-fiber = { path = "../fiber", version = "=0.39.0", optional = true }
-wasmtime-jit-debug = { path = "../jit-debug", version = "=0.39.0", features = ["gdb_jit_int"] }
+wasmtime-environ = { path = "../environ", version = "=0.40.0" }
+wasmtime-fiber = { path = "../fiber", version = "=0.40.0", optional = true }
+wasmtime-jit-debug = { path = "../jit-debug", version = "=0.40.0", features = ["gdb_jit_int"] }
 region = "2.1.0"
 libc = { version = "0.2.112", default-features = false }
 log = "0.4.8"
diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml
index 94569eb74..bd6369520 100644
--- a/crates/types/Cargo.toml
+++ b/crates/types/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-types"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "WebAssembly type definitions for Cranelift"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/wasmtime-types"
 edition = "2021"
 
 [dependencies]
-cranelift-entity = { path = "../../cranelift/entity", version = "0.86.0", features = ['enable-serde'] }
+cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0", features = ['enable-serde'] }
 serde = { version = "1.0.94", features = ["derive"] }
 thiserror = "1.0.4"
 wasmparser = { version = "0.86.0", default-features = false }
diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml
index 280456f7e..28a428c1e 100644
--- a/crates/wasi-common/Cargo.toml
+++ b/crates/wasi-common/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasi-common"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "WASI implementation in Rust"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -20,7 +20,7 @@ links = "wasi-common-19"
 [dependencies]
 anyhow = "1.0"
 thiserror = "1.0"
-wiggle = { path = "../wiggle", default-features = false, version = "=0.39.0" }
+wiggle = { path = "../wiggle", default-features = false, version = "=0.40.0" }
 tracing = "0.1.19"
 cap-std = "0.25.0"
 cap-rand = "0.25.0"
diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml
index 1fd5efd55..1f667b556 100644
--- a/crates/wasi-common/cap-std-sync/Cargo.toml
+++ b/crates/wasi-common/cap-std-sync/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasi-cap-std-sync"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "WASI implementation in Rust"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -12,7 +12,7 @@ edition = "2021"
 include = ["src/**/*", "README.md", "LICENSE" ]
 
 [dependencies]
-wasi-common = { path = "../", version = "=0.39.0" }
+wasi-common = { path = "../", version = "=0.40.0" }
 async-trait = "0.1"
 anyhow = "1.0"
 cap-std = "0.25.0"
diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml
index fb9012c1e..14224a4ec 100644
--- a/crates/wasi-common/tokio/Cargo.toml
+++ b/crates/wasi-common/tokio/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasi-tokio"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "WASI implementation in Rust"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -11,9 +11,9 @@ edition = "2021"
 include = ["src/**/*", "LICENSE" ]
 
 [dependencies]
-wasi-common = { path = "../", version = "=0.39.0" }
-wasi-cap-std-sync = { path = "../cap-std-sync", version = "=0.39.0" }
-wiggle = { path = "../../wiggle", version = "=0.39.0" }
+wasi-common = { path = "../", version = "=0.40.0" }
+wasi-cap-std-sync = { path = "../cap-std-sync", version = "=0.40.0" }
+wiggle = { path = "../../wiggle", version = "=0.40.0" }
 tokio = { version = "1.8.0", features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"] }
 cap-std = "0.25.0"
 anyhow = "1"
diff --git a/crates/wasi-crypto/Cargo.toml b/crates/wasi-crypto/Cargo.toml
index 6d6be3179..e392ea410 100644
--- a/crates/wasi-crypto/Cargo.toml
+++ b/crates/wasi-crypto/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-wasi-crypto"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Wasmtime implementation of the wasi-crypto API"
 documentation = "https://docs.rs/wasmtime-wasi-crypto"
@@ -14,8 +14,8 @@ edition = "2021"
 [dependencies]
 anyhow = "1.0"
 wasi-crypto = { path = "spec/implementations/hostcalls/rust", version = "0.1.5" }
-wasmtime = { path = "../wasmtime", version = "0.39.0", default-features = false }
-wiggle = { path = "../wiggle", version = "=0.39.0" }
+wasmtime = { path = "../wasmtime", version = "0.40.0", default-features = false }
+wiggle = { path = "../wiggle", version = "=0.40.0" }
 
 [badges]
 maintenance = { status = "experimental" }
diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml
index 613494bca..f922d49bb 100644
--- a/crates/wasi-nn/Cargo.toml
+++ b/crates/wasi-nn/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-wasi-nn"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "Wasmtime implementation of the wasi-nn API"
 documentation = "https://docs.rs/wasmtime-wasi-nn"
@@ -14,7 +14,7 @@ edition = "2021"
 [dependencies]
 # These dependencies are necessary for the witx-generation macros to work:
 anyhow = "1.0"
-wiggle = { path = "../wiggle", version = "=0.39.0" }
+wiggle = { path = "../wiggle", version = "=0.40.0" }
 
 # These dependencies are necessary for the wasi-nn implementation:
 openvino = { version = "0.3.3", features = ["runtime-linking"] }
diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml
index d0bafdd39..ba3a298e8 100644
--- a/crates/wasi/Cargo.toml
+++ b/crates/wasi/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-wasi"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "WASI implementation in Rust"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -13,11 +13,11 @@ include = ["src/**/*", "README.md", "LICENSE", "build.rs"]
 build = "build.rs"
 
 [dependencies]
-wasi-common = { path = "../wasi-common", version = "=0.39.0" }
-wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "=0.39.0", optional = true }
-wasi-tokio = { path = "../wasi-common/tokio", version = "=0.39.0", optional = true }
-wiggle = { path = "../wiggle", default-features = false, version = "=0.39.0", features = ["wasmtime_integration"] }
-wasmtime = { path = "../wasmtime", default-features = false, version = "0.39.0" }
+wasi-common = { path = "../wasi-common", version = "=0.40.0" }
+wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "=0.40.0", optional = true }
+wasi-tokio = { path = "../wasi-common/tokio", version = "=0.40.0", optional = true }
+wiggle = { path = "../wiggle", default-features = false, version = "=0.40.0", features = ["wasmtime_integration"] }
+wasmtime = { path = "../wasmtime", default-features = false, version = "0.40.0" }
 anyhow = "1.0"
 
 [features]
diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml
index 21329348f..733878d5a 100644
--- a/crates/wasmtime/Cargo.toml
+++ b/crates/wasmtime/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "High-level API to expose the Wasmtime runtime"
 documentation = "https://docs.rs/wasmtime"
@@ -13,13 +13,13 @@ edition = "2021"
 rustdoc-args = ["--cfg", "nightlydoc"]
 
 [dependencies]
-wasmtime-runtime = { path = "../runtime", version = "=0.39.0" }
-wasmtime-environ = { path = "../environ", version = "=0.39.0" }
-wasmtime-jit = { path = "../jit", version = "=0.39.0" }
-wasmtime-cache = { path = "../cache", version = "=0.39.0", optional = true }
-wasmtime-fiber = { path = "../fiber", version = "=0.39.0", optional = true }
-wasmtime-cranelift = { path = "../cranelift", version = "=0.39.0", optional = true }
-wasmtime-component-macro = { path = "../component-macro", version = "=0.39.0", optional = true }
+wasmtime-runtime = { path = "../runtime", version = "=0.40.0" }
+wasmtime-environ = { path = "../environ", version = "=0.40.0" }
+wasmtime-jit = { path = "../jit", version = "=0.40.0" }
+wasmtime-cache = { path = "../cache", version = "=0.40.0", optional = true }
+wasmtime-fiber = { path = "../fiber", version = "=0.40.0", optional = true }
+wasmtime-cranelift = { path = "../cranelift", version = "=0.40.0", optional = true }
+wasmtime-component-macro = { path = "../component-macro", version = "=0.40.0", optional = true }
 target-lexicon = { version = "0.12.0", default-features = false }
 wasmparser = "0.86.0"
 anyhow = "1.0.19"
diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs
index cfcfd6890..a5fe8de4b 100644
--- a/crates/wasmtime/src/component/func.rs
+++ b/crates/wasmtime/src/component/func.rs
@@ -212,7 +212,18 @@ impl Func {
         Return: Lift,
         S: AsContext,
     {
-        self.typecheck::<Params, Return>(store.as_context().0)?;
+        self._typed(store.as_context().0)
+    }
+
+    pub(crate) fn _typed<Params, Return>(
+        &self,
+        store: &StoreOpaque,
+    ) -> Result<TypedFunc<Params, Return>>
+    where
+        Params: ComponentParams + Lower,
+        Return: Lift,
+    {
+        self.typecheck::<Params, Return>(store)?;
         unsafe { Ok(TypedFunc::new_unchecked(*self)) }
     }
 
diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs
index 6ceb04bc4..f0711f75a 100644
--- a/crates/wasmtime/src/component/instance.rs
+++ b/crates/wasmtime/src/component/instance.rs
@@ -2,8 +2,9 @@ use crate::component::func::HostFunc;
 use crate::component::{Component, ComponentParams, Func, Lift, Lower, TypedFunc};
 use crate::instance::OwnedImports;
 use crate::store::{StoreOpaque, Stored};
-use crate::{AsContextMut, Module, StoreContext, StoreContextMut};
+use crate::{AsContextMut, Module, StoreContextMut};
 use anyhow::{anyhow, Context, Result};
+use indexmap::IndexMap;
 use std::marker;
 use std::sync::Arc;
 use wasmtime_environ::component::{
@@ -43,30 +44,33 @@ pub(crate) struct InstanceData {
 }
 
 impl Instance {
-    /// Looks up a function by name within this [`Instance`].
+    /// Returns information about the exports of this instance.
     ///
-    /// The `store` specified must be the store that this instance lives within
-    /// and `name` is the name of the function to lookup. If the function is
-    /// found `Some` is returned otherwise `None` is returned.
+    /// This method can be used to extract exported values from this component
+    /// instance. The argument to this method be a handle to the store that
+    /// this instance was instantiated into.
+    ///
+    /// The returned [`Exports`] value can be used to lookup exported items by
+    /// name.
     ///
     /// # Panics
     ///
     /// Panics if `store` does not own this instance.
-    pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option<Func> {
-        self._get_func(store.as_context_mut().0, name)
+    pub fn exports<'a, T: 'a>(&self, store: impl Into<StoreContextMut<'a, T>>) -> Exports<'a> {
+        let store = store.into();
+        Exports::new(store.0, self)
     }
 
-    fn _get_func(&self, store: &mut StoreOpaque, name: &str) -> Option<Func> {
-        // FIXME: this movement in ownership is unfortunate and feels like there
-        // should be a better solution. The reason for this is that we need to
-        // simultaneously look at lots of pieces of `InstanceData` while also
-        // inserting into `store`, but `InstanceData` is stored within `store`.
-        // By moving it out we appease the borrow-checker but take a runtime
-        // hit.
-        let data = store[self.0].take().unwrap();
-        let result = data.get_func(store, self, name);
-        store[self.0] = Some(data);
-        return result;
+    /// Looks up a function by name within this [`Instance`].
+    ///
+    /// This is a convenience method for calling [`Instance::exports`] followed
+    /// by [`ExportInstance::get_func`].
+    ///
+    /// # Panics
+    ///
+    /// Panics if `store` does not own this instance.
+    pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option<Func> {
+        self.exports(store.as_context_mut()).root().func(name)
     }
 
     /// Looks up an exported [`Func`] value by name and with its type.
@@ -97,52 +101,24 @@ impl Instance {
             .with_context(|| format!("failed to convert function `{}` to given type", name))?)
     }
 
-    /// Returns an iterator of all of the exported modules that this instance
-    /// contains.
-    //
-    // FIXME: this should probably be generalized in some form to something else
-    // that either looks like:
-    //
-    // * an iterator over all exports
-    // * an iterator for a `Component` with type information followed by a
-    //   `get_module` function here
-    //
-    // For now this is just quick-and-dirty to get wast support for iterating
-    // over exported modules to work.
-    pub fn modules<'a, T: 'a>(
-        &'a self,
-        store: impl Into<StoreContext<'a, T>>,
-    ) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
-        let store = store.into();
-        self._modules(store.0)
-    }
-
-    fn _modules<'a>(
-        &'a self,
-        store: &'a StoreOpaque,
-    ) -> impl Iterator<Item = (&'a str, &'a Module)> + '_ {
-        let data = store.store_data()[self.0].as_ref().unwrap();
-        data.component
-            .env_component()
-            .exports
-            .iter()
-            .filter_map(|(name, export)| match *export {
-                Export::Module(idx) => Some((name.as_str(), &data.exported_modules[idx])),
-                _ => None,
-            })
+    /// Looks up a module by name within this [`Instance`].
+    ///
+    /// The `store` specified must be the store that this instance lives within
+    /// and `name` is the name of the function to lookup. If the function is
+    /// found `Some` is returned otherwise `None` is returned.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `store` does not own this instance.
+    pub fn get_module(&self, mut store: impl AsContextMut, name: &str) -> Option<Module> {
+        self.exports(store.as_context_mut())
+            .root()
+            .module(name)
+            .cloned()
     }
 }
 
 impl InstanceData {
-    fn get_func(&self, store: &mut StoreOpaque, instance: &Instance, name: &str) -> Option<Func> {
-        match self.component.env_component().exports.get(name)? {
-            Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func(
-                store, instance, self, *ty, func, options,
-            )),
-            Export::Module(_) => None,
-        }
-    }
-
     pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export {
         match def {
             CoreDef::Export(e) => self.lookup_export(store, e),
@@ -442,3 +418,165 @@ impl<T> InstancePre<T> {
         Ok(Instance(store.0.store_data_mut().insert(Some(data))))
     }
 }
+
+/// Description of the exports of an [`Instance`].
+///
+/// This structure is created through the [`Instance::exports`] method and is
+/// used lookup exports by name from within an instance.
+pub struct Exports<'store> {
+    store: &'store mut StoreOpaque,
+    data: Option<Box<InstanceData>>,
+    instance: Instance,
+}
+
+impl<'store> Exports<'store> {
+    fn new(store: &'store mut StoreOpaque, instance: &Instance) -> Exports<'store> {
+        // Note that the `InstanceData` is `take`n from the store here. That's
+        // to ease with the various liftimes in play here where we often need
+        // simultaneous borrows into the `store` and the `data`.
+        //
+        // To put the data back into the store the `Drop for Exports<'_>` will
+        // restore the state of the world.
+        Exports {
+            data: store[instance.0].take(),
+            store,
+            instance: *instance,
+        }
+    }
+
+    /// Returns the "root" instance of this set of exports, or the items that
+    /// are directly exported from the instance that this was created from.
+    pub fn root(&mut self) -> ExportInstance<'_, '_> {
+        let data = self.data.as_ref().unwrap();
+        ExportInstance {
+            exports: &data.component.env_component().exports,
+            instance: &self.instance,
+            data,
+            store: self.store,
+        }
+    }
+
+    /// Returns the items that the named instance exports.
+    ///
+    /// This method will lookup the exported instance with the name `name` from
+    /// this list of exports and return a descriptin of that instance's
+    /// exports.
+    pub fn instance(&mut self, name: &str) -> Option<ExportInstance<'_, '_>> {
+        self.root().into_instance(name)
+    }
+
+    // FIXME: should all the func/module/typed_func methods below be mirrored
+    // here as well? They're already mirrored on `Instance` and otherwise
+    // this is attempting to look like the `Linker` API "but in reverse"
+    // somewhat.
+}
+
+impl Drop for Exports<'_> {
+    fn drop(&mut self) {
+        // See `Exports::new` for where this data was originally extracted, and
+        // this is just restoring the state of the world.
+        self.store[self.instance.0] = self.data.take();
+    }
+}
+
+/// Description of the exports of a single instance.
+///
+/// This structure is created from [`Exports`] via the [`Exports::root`] or
+/// [`Exports::instance`] methods. This type provides access to the first layer
+/// of exports within an instance. The [`ExportInstance::instance`] method
+/// can be used to provide nested access to sub-instances.
+pub struct ExportInstance<'a, 'store> {
+    exports: &'a IndexMap<String, Export>,
+    instance: &'a Instance,
+    data: &'a InstanceData,
+    store: &'store mut StoreOpaque,
+}
+
+impl<'a, 'store> ExportInstance<'a, 'store> {
+    /// Same as [`Instance::get_func`]
+    pub fn func(&mut self, name: &str) -> Option<Func> {
+        match self.exports.get(name)? {
+            Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func(
+                self.store,
+                self.instance,
+                self.data,
+                *ty,
+                func,
+                options,
+            )),
+            Export::Module(_) | Export::Instance(_) => None,
+        }
+    }
+
+    /// Same as [`Instance::get_typed_func`]
+    pub fn typed_func<Params, Results>(&mut self, name: &str) -> Result<TypedFunc<Params, Results>>
+    where
+        Params: ComponentParams + Lower,
+        Results: Lift,
+    {
+        let func = self
+            .func(name)
+            .ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
+        Ok(func
+            ._typed::<Params, Results>(self.store)
+            .with_context(|| format!("failed to convert function `{}` to given type", name))?)
+    }
+
+    /// Same as [`Instance::get_module`]
+    pub fn module(&mut self, name: &str) -> Option<&'a Module> {
+        match self.exports.get(name)? {
+            Export::Module(idx) => Some(&self.data.exported_modules[*idx]),
+            _ => None,
+        }
+    }
+
+    /// Returns an iterator of all of the exported modules that this instance
+    /// contains.
+    //
+    // FIXME: this should probably be generalized in some form to something else
+    // that either looks like:
+    //
+    // * an iterator over all exports
+    // * an iterator for a `Component` with type information followed by a
+    //   `get_module` function here
+    //
+    // For now this is just quick-and-dirty to get wast support for iterating
+    // over exported modules to work.
+    pub fn modules(&self) -> impl Iterator<Item = (&'a str, &'a Module)> + '_ {
+        self.exports
+            .iter()
+            .filter_map(|(name, export)| match *export {
+                Export::Module(idx) => Some((name.as_str(), &self.data.exported_modules[idx])),
+                _ => None,
+            })
+    }
+
+    fn as_mut(&mut self) -> ExportInstance<'a, '_> {
+        ExportInstance {
+            exports: self.exports,
+            instance: self.instance,
+            data: self.data,
+            store: self.store,
+        }
+    }
+
+    /// Looks up the exported instance with the `name` specified and returns
+    /// a description of its exports.
+    pub fn instance(&mut self, name: &str) -> Option<ExportInstance<'a, '_>> {
+        self.as_mut().into_instance(name)
+    }
+
+    /// Same as [`ExportInstance::instance`] but consumes self to yield a
+    /// return value with the same lifetimes.
+    pub fn into_instance(self, name: &str) -> Option<ExportInstance<'a, 'store>> {
+        match self.exports.get(name)? {
+            Export::Instance(exports) => Some(ExportInstance {
+                exports,
+                instance: self.instance,
+                data: self.data,
+                store: self.store,
+            }),
+            _ => None,
+        }
+    }
+}
diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs
index f2193afe8..a96d3f1d9 100644
--- a/crates/wasmtime/src/component/mod.rs
+++ b/crates/wasmtime/src/component/mod.rs
@@ -14,7 +14,7 @@ pub use self::func::{
     ComponentParams, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList,
     WasmStr,
 };
-pub use self::instance::{Instance, InstancePre};
+pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
 pub use self::linker::{Linker, LinkerInstance};
 pub use wasmtime_component_macro::{ComponentType, Lift, Lower};
 
diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml
index 85e473992..24c475142 100644
--- a/crates/wast/Cargo.toml
+++ b/crates/wast/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmtime-wast"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["The Wasmtime Project Developers"]
 description = "wast testing support for wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -11,7 +11,7 @@ edition = "2021"
 
 [dependencies]
 anyhow = "1.0.19"
-wasmtime = { path = "../wasmtime", version = "0.39.0", default-features = false, features = ['cranelift'] }
+wasmtime = { path = "../wasmtime", version = "0.40.0", default-features = false, features = ['cranelift'] }
 wast = "42.0.0"
 
 [badges]
diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs
index e318f87bb..a88583c89 100644
--- a/crates/wast/src/wast.rs
+++ b/crates/wast/src/wast.rs
@@ -193,7 +193,7 @@ impl<T> WastContext<T> {
                     // function from one instance and put it into the linker
                     // (must go through the host right now).
                     let mut linker = self.component_linker.instance(name.name())?;
-                    for (name, module) in instance.modules(&self.store) {
+                    for (name, module) in instance.exports(&mut self.store).root().modules() {
                         linker.module(name, module)?;
                     }
                 }
diff --git a/crates/wiggle/Cargo.toml b/crates/wiggle/Cargo.toml
index 492f74f64..c7e2d9056 100644
--- a/crates/wiggle/Cargo.toml
+++ b/crates/wiggle/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wiggle"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkonk@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
 edition = "2021"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -13,11 +13,11 @@ include = ["src/**/*", "README.md", "LICENSE"]
 [dependencies]
 thiserror = "1"
 witx = { path = "../wasi-common/WASI/tools/witx", version = "0.9.1", optional = true }
-wiggle-macro = { path = "macro", version = "=0.39.0" }
+wiggle-macro = { path = "macro", version = "=0.40.0" }
 tracing = "0.1.26"
 bitflags = "1.2"
 async-trait = "0.1.42"
-wasmtime = { path = "../wasmtime", version = "0.39.0", optional = true, default-features = false }
+wasmtime = { path = "../wasmtime", version = "0.40.0", optional = true, default-features = false }
 anyhow = "1.0"
 
 [badges]
diff --git a/crates/wiggle/generate/Cargo.toml b/crates/wiggle/generate/Cargo.toml
index 3e1eb6285..af3ac56af 100644
--- a/crates/wiggle/generate/Cargo.toml
+++ b/crates/wiggle/generate/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wiggle-generate"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
 license = "Apache-2.0 WITH LLVM-exception"
 edition = "2021"
diff --git a/crates/wiggle/macro/Cargo.toml b/crates/wiggle/macro/Cargo.toml
index 81f3bfee3..f2b6df1d6 100644
--- a/crates/wiggle/macro/Cargo.toml
+++ b/crates/wiggle/macro/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wiggle-macro"
-version = "0.39.0"
+version = "0.40.0"
 authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
 edition = "2021"
 license = "Apache-2.0 WITH LLVM-exception"
@@ -21,7 +21,7 @@ test = false
 doctest = false
 
 [dependencies]
-wiggle-generate = { path = "../generate", version = "=0.39.0" }
+wiggle-generate = { path = "../generate", version = "=0.40.0" }
 quote = "1.0"
 syn = { version = "1.0", features = ["full"] }
 proc-macro2 = "1.0"


Test patch:
diff --git a/cranelift/codegen/src/isa/aarch64/inst/emit_tests.rs b/cranelift/codegen/src/isa/aarch64/inst/emit_tests.rs
index 2d58996fc..c1a124ec8 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/emit_tests.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/emit_tests.rs
@@ -3576,6 +3576,18 @@ fn test_aarch64_binemit() {
         "sshl v8.2d, v22.2d, v2.2d",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umin,
+            rd: writable_vreg(0),
+            rn: vreg(11),
+            rm: vreg(2),
+            size: VectorSize::Size8x8,
+        },
+        "606D222E",
+        "umin v0.8b, v11.8b, v2.8b",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Umin,
@@ -3588,6 +3600,18 @@ fn test_aarch64_binemit() {
         "umin v1.16b, v12.16b, v3.16b",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umin,
+            rd: writable_vreg(29),
+            rn: vreg(19),
+            rm: vreg(9),
+            size: VectorSize::Size16x4,
+        },
+        "7D6E692E",
+        "umin v29.4h, v19.4h, v9.4h",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Umin,
@@ -3600,6 +3624,18 @@ fn test_aarch64_binemit() {
         "umin v30.8h, v20.8h, v10.8h",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umin,
+            rd: writable_vreg(7),
+            rn: vreg(21),
+            rm: vreg(20),
+            size: VectorSize::Size32x2,
+        },
+        "A76EB42E",
+        "umin v7.2s, v21.2s, v20.2s",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Umin,
@@ -3612,6 +3648,18 @@ fn test_aarch64_binemit() {
         "umin v8.4s, v22.4s, v21.4s",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smin,
+            rd: writable_vreg(2),
+            rn: vreg(13),
+            rm: vreg(4),
+            size: VectorSize::Size8x8,
+        },
+        "A26D240E",
+        "smin v2.8b, v13.8b, v4.8b",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smin,
@@ -3624,6 +3672,18 @@ fn test_aarch64_binemit() {
         "smin v1.16b, v12.16b, v3.16b",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smin,
+            rd: writable_vreg(3),
+            rn: vreg(2),
+            rm: vreg(1),
+            size: VectorSize::Size16x4,
+        },
+        "436C610E",
+        "smin v3.4h, v2.4h, v1.4h",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smin,
@@ -3636,6 +3696,18 @@ fn test_aarch64_binemit() {
         "smin v30.8h, v20.8h, v10.8h",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smin,
+            rd: writable_vreg(9),
+            rn: vreg(22),
+            rm: vreg(20),
+            size: VectorSize::Size32x2,
+        },
+        "C96EB40E",
+        "smin v9.2s, v22.2s, v20.2s",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smin,
@@ -3660,6 +3732,30 @@ fn test_aarch64_binemit() {
         "umax v6.8b, v9.8b, v8.8b",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umax,
+            rd: writable_vreg(5),
+            rn: vreg(15),
+            rm: vreg(8),
+            size: VectorSize::Size8x16,
+        },
+        "E565286E",
+        "umax v5.16b, v15.16b, v8.16b",
+    ));
+
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umax,
+            rd: writable_vreg(12),
+            rn: vreg(14),
+            rm: vreg(3),
+            size: VectorSize::Size16x4,
+        },
+        "CC65632E",
+        "umax v12.4h, v14.4h, v3.4h",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Umax,
@@ -3672,6 +3768,18 @@ fn test_aarch64_binemit() {
         "umax v11.8h, v13.8h, v2.8h",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Umax,
+            rd: writable_vreg(9),
+            rn: vreg(13),
+            rm: vreg(15),
+            size: VectorSize::Size32x2,
+        },
+        "A965AF2E",
+        "umax v9.2s, v13.2s, v15.2s",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Umax,
@@ -3684,6 +3792,18 @@ fn test_aarch64_binemit() {
         "umax v8.4s, v12.4s, v14.4s",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smax,
+            rd: writable_vreg(7),
+            rn: vreg(8),
+            rm: vreg(9),
+            size: VectorSize::Size8x8,
+        },
+        "0765290E",
+        "smax v7.8b, v8.8b, v9.8b",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smax,
@@ -3696,6 +3816,18 @@ fn test_aarch64_binemit() {
         "smax v6.16b, v9.16b, v8.16b",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smax,
+            rd: writable_vreg(11),
+            rn: vreg(12),
+            rm: vreg(13),
+            size: VectorSize::Size16x4,
+        },
+        "8B656D0E",
+        "smax v11.4h, v12.4h, v13.4h",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smax,
@@ -3708,6 +3840,18 @@ fn test_aarch64_binemit() {
         "smax v11.8h, v13.8h, v2.8h",
     ));
 
+    insns.push((
+        Inst::VecRRR {
+            alu_op: VecALUOp::Smax,
+            rd: writable_vreg(14),
+            rn: vreg(16),
+            rm: vreg(18),
+            size: VectorSize::Size32x2,
+        },
+        "0E66B20E",
+        "smax v14.2s, v16.2s, v18.2s",
+    ));
+
     insns.push((
         Inst::VecRRR {
             alu_op: VecALUOp::Smax,
diff --git a/cranelift/filetests/Cargo.toml b/cranelift/filetests/Cargo.toml
index cf659222c..7f87f195e 100644
--- a/cranelift/filetests/Cargo.toml
+++ b/cranelift/filetests/Cargo.toml
@@ -10,12 +10,12 @@ publish = false
 edition = "2021"
 
 [dependencies]
-cranelift-codegen = { path = "../codegen", version = "0.86.0", features = ["testing_hooks"] }
-cranelift-frontend = { path = "../frontend", version = "0.86.0" }
-cranelift-interpreter = { path = "../interpreter", version = "0.86.0" }
-cranelift-native = { path = "../native", version = "0.86.0" }
-cranelift-reader = { path = "../reader", version = "0.86.0" }
-cranelift-preopt = { path = "../preopt", version = "0.86.0" }
+cranelift-codegen = { path = "../codegen", version = "0.87.0", features = ["testing_hooks"] }
+cranelift-frontend = { path = "../frontend", version = "0.87.0" }
+cranelift-interpreter = { path = "../interpreter", version = "0.87.0" }
+cranelift-native = { path = "../native", version = "0.87.0" }
+cranelift-reader = { path = "../reader", version = "0.87.0" }
+cranelift-preopt = { path = "../preopt", version = "0.87.0" }
 file-per-thread-logger = "0.1.2"
 filecheck = "0.5.0"
 gimli = { version = "0.26.0", default-features = false, features = ["read"] }
diff --git a/cranelift/filetests/filetests/isa/aarch64/simd-min-max.clif b/cranelift/filetests/filetests/isa/aarch64/simd-min-max.clif
new file mode 100644
index 000000000..9a42439c1
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/aarch64/simd-min-max.clif
@@ -0,0 +1,244 @@
+test compile precise-output
+set unwind_info=false
+target aarch64
+
+function %fn0(i8x8, i8x8) -> i8x8 {
+block0(v0: i8x8, v1: i8x8):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.8b, v0.8b, v1.8b
+;   ret
+
+function %fn1(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.16b, v0.16b, v1.16b
+;   ret
+
+function %fn2(i16x4, i16x4) -> i16x4 {
+block0(v0: i16x4, v1: i16x4):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.4h, v0.4h, v1.4h
+;   ret
+
+function %fn3(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.8h, v0.8h, v1.8h
+;   ret
+
+function %fn4(i32x2, i32x2) -> i32x2 {
+block0(v0: i32x2, v1: i32x2):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.2s, v0.2s, v1.2s
+;   ret
+
+function %fn5(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = imin v0, v1
+  return v2
+}
+
+; block0:
+;   smin v0.4s, v0.4s, v1.4s
+;   ret
+
+function %fn6(i8x8, i8x8) -> i8x8 {
+block0(v0: i8x8, v1: i8x8):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.8b, v0.8b, v1.8b
+;   ret
+
+function %fn7(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.16b, v0.16b, v1.16b
+;   ret
+
+function %fn8(i16x4, i16x4) -> i16x4 {
+block0(v0: i16x4, v1: i16x4):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.4h, v0.4h, v1.4h
+;   ret
+
+function %fn9(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.8h, v0.8h, v1.8h
+;   ret
+
+function %fn10(i32x2, i32x2) -> i32x2 {
+block0(v0: i32x2, v1: i32x2):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.2s, v0.2s, v1.2s
+;   ret
+
+function %fn11(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = umin v0, v1
+  return v2
+}
+
+; block0:
+;   umin v0.4s, v0.4s, v1.4s
+;   ret
+
+function %fn12(i8x8, i8x8) -> i8x8 {
+block0(v0: i8x8, v1: i8x8):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.8b, v0.8b, v1.8b
+;   ret
+
+function %fn13(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.16b, v0.16b, v1.16b
+;   ret
+
+function %fn14(i16x4, i16x4) -> i16x4 {
+block0(v0: i16x4, v1: i16x4):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.4h, v0.4h, v1.4h
+;   ret
+
+function %fn15(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.8h, v0.8h, v1.8h
+;   ret
+
+function %fn16(i32x2, i32x2) -> i32x2 {
+block0(v0: i32x2, v1: i32x2):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.2s, v0.2s, v1.2s
+;   ret
+
+function %fn17(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = imax v0, v1
+  return v2
+}
+
+; block0:
+;   smax v0.4s, v0.4s, v1.4s
+;   ret
+
+function %fn18(i8x8, i8x8) -> i8x8 {
+block0(v0: i8x8, v1: i8x8):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.8b, v0.8b, v1.8b
+;   ret
+
+function %fn19(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.16b, v0.16b, v1.16b
+;   ret
+
+function %fn20(i16x4, i16x4) -> i16x4 {
+block0(v0: i16x4, v1: i16x4):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.4h, v0.4h, v1.4h
+;   ret
+
+function %fn21(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.8h, v0.8h, v1.8h
+;   ret
+
+function %fn22(i32x2, i32x2) -> i32x2 {
+block0(v0: i32x2, v1: i32x2):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.2s, v0.2s, v1.2s
+;   ret
+
+function %fn23(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = umax v0, v1
+  return v2
+}
+
+; block0:
+;   umax v0.4s, v0.4s, v1.4s
+;   ret
+
diff --git a/cranelift/filetests/filetests/runtests/fabs.clif b/cranelift/filetests/filetests/runtests/fabs.clif
new file mode 100644
index 000000000..4d63273ef
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/fabs.clif
@@ -0,0 +1,93 @@
+test interpret
+test run
+target aarch64
+target x86_64
+target s390x
+
+function %fabs_f32(f32) -> f32 {
+block0(v0: f32):
+    v1 = fabs v0
+    return v1
+}
+; run: %fabs_f32(0x9.0) == 0x9.0
+; run: %fabs_f32(-0x9.0) == 0x9.0
+; run: %fabs_f32(0x0.0) == 0x0.0
+; run: %fabs_f32(-0x0.0) == 0x0.0
+
+; F32 Inf
+; run: %fabs_f32(Inf) == Inf
+; run: %fabs_f32(-Inf) == +Inf
+
+; F32 Epsilon  / Max / Min Positive
+; run: %fabs_f32(0x1.000000p-23) == 0x1.000000p-23
+; run: %fabs_f32(-0x1.000000p-23) == 0x1.000000p-23
+; run: %fabs_f32(0x1.fffffep127) == 0x1.fffffep127
+; run: %fabs_f32(-0x1.fffffep127) == 0x1.fffffep127
+; run: %fabs_f32(0x1.000000p-126) == 0x1.000000p-126
+; run: %fabs_f32(-0x1.000000p-126) == 0x1.000000p-126
+
+; F32 Subnormals
+; run: %fabs_f32(0x0.800000p-126) == 0x0.800000p-126
+; run: %fabs_f32(-0x0.800000p-126) == 0x0.800000p-126
+; run: %fabs_f32(0x0.000002p-126) == 0x0.000002p-126
+; run: %fabs_f32(-0x0.000002p-126) == 0x0.000002p-126
+
+; F32 NaN's
+; Unlike with other operations fabs is guaranteed to only affect the sign bit
+; run: %fabs_f32(+NaN) == +NaN
+; run: %fabs_f32(-NaN) == +NaN
+; run: %fabs_f32(+NaN:0x0) == +NaN:0x0
+; run: %fabs_f32(+NaN:0x1) == +NaN:0x1
+; run: %fabs_f32(+NaN:0x300001) == +NaN:0x300001
+; run: %fabs_f32(-NaN:0x0) == +NaN:0x0
+; run: %fabs_f32(-NaN:0x1) == +NaN:0x1
+; run: %fabs_f32(-NaN:0x300001) == +NaN:0x300001
+; run: %fabs_f32(+sNaN:0x1) == +sNaN:0x1
+; run: %fabs_f32(-sNaN:0x1) == +sNaN:0x1
+; run: %fabs_f32(+sNaN:0x200001) == +sNaN:0x200001
+; run: %fabs_f32(-sNaN:0x200001) == +sNaN:0x200001
+
+
+
+function %fabs_f64(f64) -> f64 {
+block0(v0: f64):
+    v1 = fabs v0
+    return v1
+}
+; run: %fabs_f64(0x9.0) == 0x9.0
+; run: %fabs_f64(-0x9.0) == 0x9.0
+; run: %fabs_f64(0x0.0) == 0x0.0
+; run: %fabs_f64(-0x0.0) == 0x0.0
+
+; F64 Inf
+; run: %fabs_f64(Inf) == Inf
+; run: %fabs_f64(-Inf) == +Inf
+
+; F64 Epsilon / Max / Min Positive
+; run: %fabs_f64(0x1.0000000000000p-52) == 0x1.0000000000000p-52
+; run: %fabs_f64(-0x1.0000000000000p-52) == 0x1.0000000000000p-52
+; run: %fabs_f64(0x1.fffffffffffffp1023) == 0x1.fffffffffffffp1023
+; run: %fabs_f64(-0x1.fffffffffffffp1023) == 0x1.fffffffffffffp1023
+; run: %fabs_f64(0x1.0000000000000p-1022) == 0x1.0000000000000p-1022
+; run: %fabs_f64(-0x1.0000000000000p-1022) == 0x1.0000000000000p-1022
+
+; F64 Subnormals
+; run: %fabs_f64(0x0.8000000000000p-1022) == 0x0.8000000000000p-1022
+; run: %fabs_f64(-0x0.8000000000000p-1022) == 0x0.8000000000000p-1022
+; run: %fabs_f64(0x0.0000000000001p-1022) == 0x0.0000000000001p-1022
+; run: %fabs_f64(-0x0.0000000000001p-1022) == 0x0.0000000000001p-1022
+
+; F64 NaN's
+; Unlike with other operations fabs is guaranteed to only affect the sign bit
+; run: %fabs_f64(+NaN) == +NaN
+; run: %fabs_f64(-NaN) == +NaN
+; run: %fabs_f64(+NaN:0x0) == +NaN:0x0
+; run: %fabs_f64(+NaN:0x1) == +NaN:0x1
+; run: %fabs_f64(+NaN:0x4000000000001) == +NaN:0x4000000000001
+; run: %fabs_f64(-NaN:0x0) == +NaN:0x0
+; run: %fabs_f64(-NaN:0x1) == +NaN:0x1
+; run: %fabs_f64(-NaN:0x4000000000001) == +NaN:0x4000000000001
+; run: %fabs_f64(+sNaN:0x1) == +sNaN:0x1
+; run: %fabs_f64(-sNaN:0x1) == +sNaN:0x1
+; run: %fabs_f64(+sNaN:0x4000000000001) == +sNaN:0x4000000000001
+; run: %fabs_f64(-sNaN:0x4000000000001) == +sNaN:0x4000000000001
diff --git a/cranelift/filetests/filetests/runtests/fcopysign.clif b/cranelift/filetests/filetests/runtests/fcopysign.clif
new file mode 100644
index 000000000..281143783
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/fcopysign.clif
@@ -0,0 +1,107 @@
+test interpret
+test run
+target aarch64
+target x86_64
+target s390x
+
+function %fcopysign_f32(f32, f32) -> f32 {
+block0(v0: f32, v1: f32):
+    v2 = fcopysign v0, v1
+    return v2
+}
+; run: %fcopysign_f32(0x9.0, 0x9.0) == 0x9.0
+; run: %fcopysign_f32(-0x9.0, 0x9.0) == 0x9.0
+; run: %fcopysign_f32(0x9.0, -0x9.0) == -0x9.0
+; run: %fcopysign_f32(-0x9.0, -0x9.0) == -0x9.0
+; run: %fcopysign_f32(0x0.0, -0x0.0) == -0x0.0
+; run: %fcopysign_f32(-0x0.0, 0x0.0) == 0x0.0
+
+; F32 Inf
+; run: %fcopysign_f32(Inf, Inf) == Inf
+; run: %fcopysign_f32(-Inf, Inf) == Inf
+; run: %fcopysign_f32(Inf, -Inf) == -Inf
+; run: %fcopysign_f32(-Inf, -Inf) == -Inf
+
+; F32 Epsilon  / Max / Min Positive
+; run: %fcopysign_f32(0x1.000000p-23, -0x0.0) == -0x1.000000p-23
+; run: %fcopysign_f32(-0x1.000000p-23, 0x0.0) == 0x1.000000p-23
+; run: %fcopysign_f32(0x1.fffffep127, -0x0.0) == -0x1.fffffep127
+; run: %fcopysign_f32(-0x1.fffffep127, 0x0.0) == 0x1.fffffep127
+; run: %fcopysign_f32(0x1.000000p-126, -0x0.0) == -0x1.000000p-126
+; run: %fcopysign_f32(-0x1.000000p-126, 0x0.0) == 0x1.000000p-126
+
+; F32 Subnormals
+; run: %fcopysign_f32(0x0.800000p-126, -0x0.0) == -0x0.800000p-126
+; run: %fcopysign_f32(-0x0.800000p-126, 0x0.0) == 0x0.800000p-126
+; run: %fcopysign_f32(0x0.000002p-126, -0x0.0) == -0x0.000002p-126
+; run: %fcopysign_f32(-0x0.000002p-126, 0x0.0) == 0x0.000002p-126
+
+; F32 NaN's
+; Unlike with other operations fcopysign is guaranteed to only affect the sign bit
+; run: %fcopysign_f32(0x0.0, -NaN) == -0x0.0
+; run: %fcopysign_f32(0x3.0, +sNaN:0x1) == 0x3.0
+; run: %fcopysign_f32(Inf, -NaN) == -Inf
+; run: %fcopysign_f32(+NaN, -NaN) == -NaN
+; run: %fcopysign_f32(-NaN, +NaN) == +NaN
+; run: %fcopysign_f32(+NaN:0x0, -NaN) == -NaN:0x0
+; run: %fcopysign_f32(+NaN:0x1, -NaN) == -NaN:0x1
+; run: %fcopysign_f32(+NaN:0x300001, -NaN) == -NaN:0x300001
+; run: %fcopysign_f32(-NaN:0x0, +NaN) == +NaN:0x0
+; run: %fcopysign_f32(-NaN:0x1, +NaN) == +NaN:0x1
+; run: %fcopysign_f32(-NaN:0x300001, +NaN) == +NaN:0x300001
+; run: %fcopysign_f32(+sNaN:0x1, -NaN) == -sNaN:0x1
+; run: %fcopysign_f32(-sNaN:0x1, +NaN) == +sNaN:0x1
+; run: %fcopysign_f32(+sNaN:0x200001, -NaN) == -sNaN:0x200001
+; run: %fcopysign_f32(-sNaN:0x200001, +NaN) == +sNaN:0x200001
+
+
+
+function %fcopysign_f64(f64, f64) -> f64 {
+block0(v0: f64, v1: f64):
+    v2 = fcopysign v0, v1
+    return v2
+}
+; run: %fcopysign_f64(0x9.0, 0x9.0) == 0x9.0
+; run: %fcopysign_f64(-0x9.0, 0x9.0) == 0x9.0
+; run: %fcopysign_f64(0x9.0, -0x9.0) == -0x9.0
+; run: %fcopysign_f64(-0x9.0, -0x9.0) == -0x9.0
+; run: %fcopysign_f64(0x0.0, -0x0.0) == -0x0.0
+; run: %fcopysign_f64(-0x0.0, 0x0.0) == 0x0.0
+
+; F64 Inf
+; run: %fcopysign_f64(Inf, Inf) == Inf
+; run: %fcopysign_f64(-Inf, Inf) == Inf
+; run: %fcopysign_f64(Inf, -Inf) == -Inf
+; run: %fcopysign_f64(-Inf, -Inf) == -Inf
+
+; F64 Epsilon / Max / Min Positive
+; run: %fcopysign_f64(0x1.0000000000000p-52, -0x0.0) == -0x1.0000000000000p-52
+; run: %fcopysign_f64(-0x1.0000000000000p-52, 0x0.0) == 0x1.0000000000000p-52
+; run: %fcopysign_f64(0x1.fffffffffffffp1023, -0x0.0) == -0x1.fffffffffffffp1023
+; run: %fcopysign_f64(-0x1.fffffffffffffp1023, 0x0.0) == 0x1.fffffffffffffp1023
+; run: %fcopysign_f64(0x1.0000000000000p-1022, -0x0.0) == -0x1.0000000000000p-1022
+; run: %fcopysign_f64(-0x1.0000000000000p-1022, 0x0.0) == 0x1.0000000000000p-1022
+
+; F64 Subnormals
+; run: %fcopysign_f64(0x0.8000000000000p-1022, -0x0.0) == -0x0.8000000000000p-1022
+; run: %fcopysign_f64(-0x0.8000000000000p-1022, 0x0.0) == 0x0.8000000000000p-1022
+; run: %fcopysign_f64(0x0.0000000000001p-1022, -0x0.0) == -0x0.0000000000001p-1022
+; run: %fcopysign_f64(-0x0.0000000000001p-1022, 0x0.0) == 0x0.0000000000001p-1022
+
+; F64 NaN's
+; Unlike with other operations fcopysign is guaranteed to only affect the sign bit
+; run: %fcopysign_f64(0x0.0, -NaN) == -0x0.0
+; run: %fcopysign_f64(0x3.0, +sNaN:0x1) == 0x3.0
+; run: %fcopysign_f64(Inf, -NaN) == -Inf
+; run: %fcopysign_f64(+NaN, -NaN) == -NaN
+; run: %fcopysign_f64(-NaN, +NaN) == +NaN
+; run: %fcopysign_f64(+NaN:0x0, -NaN) == -NaN:0x0
+; run: %fcopysign_f64(+NaN:0x1, -NaN) == -NaN:0x1
+; run: %fcopysign_f64(+NaN:0x4000000000001, -NaN) == -NaN:0x4000000000001
+; run: %fcopysign_f64(-NaN:0x0, +NaN) == +NaN:0x0
+; run: %fcopysign_f64(-NaN:0x1, +NaN) == +NaN:0x1
+; run: %fcopysign_f64(-NaN:0x4000000000001, +NaN) == +NaN:0x4000000000001
+; run: %fcopysign_f64(+sNaN:0x1, -NaN) == -sNaN:0x1
+; run: %fcopysign_f64(-sNaN:0x1, +NaN) == +sNaN:0x1
+; run: %fcopysign_f64(+sNaN:0x4000000000001, -NaN) == -sNaN:0x4000000000001
+; run: %fcopysign_f64(-sNaN:0x4000000000001, +NaN) == +sNaN:0x4000000000001
diff --git a/cranelift/filetests/filetests/runtests/fma.clif b/cranelift/filetests/filetests/runtests/fma.clif
new file mode 100644
index 000000000..e9429f4b5
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/fma.clif
@@ -0,0 +1,119 @@
+test interpret
+test run
+target aarch64
+target s390x
+
+function %fma_f32(f32, f32, f32) -> f32 {
+block0(v0: f32, v1: f32, v2: f32):
+    v3 = fma v0, v1, v2
+    return v3
+}
+; run: %fma_f32(0x9.0, 0x9.0, 0x9.0) == 0x1.680000p6
+; run: %fma_f32(0x83.0, 0x2.68091p6, 0x9.88721p1) == 0x1.3b88e6p14
+
+; run: %fma_f32(0x0.0, 0x0.0, 0x0.0) == 0x0.0
+; run: %fma_f32(0x0.0, 0x0.0, -0x0.0) == 0x0.0
+; run: %fma_f32(0x0.0, -0x0.0, 0x0.0) == 0x0.0
+; run: %fma_f32(-0x0.0, 0x0.0, 0x0.0) == 0x0.0
+
+; run: %fma_f32(-Inf, -Inf, 0x0.0) == +Inf
+; run: %fma_f32(Inf, -Inf, 0x0.0) == -Inf
+; run: %fma_f32(-Inf, Inf, 0x0.0) == -Inf
+; run: %fma_f32(Inf, -Inf, -Inf) == -Inf
+; run: %fma_f32(-Inf, Inf, -Inf) == -Inf
+
+; F32 Epsilon / Max / Min Positive
+; run: %fma_f32(0x1.000000p-23, 0x1.000000p-23, 0x1.000000p-23) == 0x1.000002p-23
+; run: %fma_f32(0x0.0, 0x0.0, 0x1.000000p-23) == 0x1.000000p-23
+; run: %fma_f32(0x1.fffffep127, 0x1.fffffep127, 0x1.fffffep127) == +Inf
+; run: %fma_f32(0x0.0, 0x0.0, 0x1.fffffep127) == 0x1.fffffep127
+; run: %fma_f32(0x1.000000p-126, 0x1.000000p-126, 0x1.000000p-126) == 0x1.000000p-126
+; run: %fma_f32(0x0.0, 0x0.0, 0x1.000000p-126) == 0x1.000000p-126
+
+; F32 Subnormals
+; run: %fma_f32(0x0.800000p-126, 0x0.800000p-126, 0x0.800000p-126) == 0x0.800000p-126
+; run: %fma_f32(0x0.800000p-126, 0x0.800000p-126, 0x0.0) == 0x0.0
+; run: %fma_f32(0x0.0, 0x0.0, 0x0.800000p-126) == 0x0.800000p-126
+; run: %fma_f32(0x0.000002p-126, 0x0.000002p-126, 0x0.000002p-126) == 0x0.000002p-126
+; run: %fma_f32(0x0.000002p-126, 0x0.000002p-126, 0x0.0) == 0x0.0
+; run: %fma_f32(0x0.0, 0x0.0, 0x0.000002p-126) == 0x0.000002p-126
+
+
+
+;; The IEEE754 Standard does not make a lot of guarantees about what
+;; comes out of NaN producing operations, we just check if its a NaN
+function %fma_is_nan_f32(f32, f32, f32) -> i32 {
+block0(v0: f32, v1: f32, v2: f32):
+    v3 = fma v0, v1, v2
+    v4 = fcmp ne v3, v3
+    v5 = bint.i32 v4
+    return v5
+}
+; run: %fma_is_nan_f32(Inf, -Inf, Inf) == 1
+; run: %fma_is_nan_f32(-Inf, Inf, Inf) == 1
+; run: %fma_is_nan_f32(-Inf, -Inf, -Inf) == 1
+
+; run: %fma_is_nan_f32(+NaN, 0x0.0, 0x0.0) == 1
+; run: %fma_is_nan_f32(0x0.0, +NaN, 0x0.0) == 1
+; run: %fma_is_nan_f32(0x0.0, 0x0.0, +NaN) == 1
+; run: %fma_is_nan_f32(-NaN, 0x0.0, 0x0.0) == 1
+; run: %fma_is_nan_f32(0x0.0, -NaN, 0x0.0) == 1
+; run: %fma_is_nan_f32(0x0.0, 0x0.0, -NaN) == 1
+
+
+
+function %fma_f64(f64, f64, f64) -> f64 {
+block0(v0: f64, v1: f64, v2: f64):
+    v3 = fma v0, v1, v2
+    return v3
+}
+; run: %fma_f64(0x9.0, 0x9.0, 0x9.0) == 0x1.680000p6
+; run: %fma_f64(0x1.3b88ea148dd4ap14, 0x2.680916809121p6, 0x9.887218721837p1) == 0x1.7ba6ebee17417p21
+
+; run: %fma_f64(0x0.0, 0x0.0, 0x0.0) == 0x0.0
+; run: %fma_f64(0x0.0, 0x0.0, -0x0.0) == 0x0.0
+; run: %fma_f64(0x0.0, -0x0.0, 0x0.0) == 0x0.0
+; run: %fma_f64(-0x0.0, 0x0.0, 0x0.0) == 0x0.0
+
+; run: %fma_f64(-Inf, -Inf, 0x0.0) == +Inf
+; run: %fma_f64(Inf, -Inf, 0x0.0) == -Inf
+; run: %fma_f64(-Inf, Inf, 0x0.0) == -Inf
+; run: %fma_f64(Inf, -Inf, -Inf) == -Inf
+; run: %fma_f64(-Inf, Inf, -Inf) == -Inf
+
+; F64 Epsilon / Max / Min Positive
+; run: %fma_f64(0x1.0000000000000p-52, 0x1.0000000000000p-52, 0x1.0000000000000p-52) == 0x1.0000000000001p-52
+; run: %fma_f64(0x0.0, 0x0.0, 0x1.0000000000000p-52) == 0x1.0000000000000p-52
+; run: %fma_f64(0x1.fffffffffffffp1023, 0x1.fffffffffffffp1023, 0x1.fffffffffffffp1023) == +Inf
+; run: %fma_f64(0x0.0, 0x0.0, 0x1.fffffffffffffp1023) == 0x1.fffffffffffffp1023
+; run: %fma_f64(0x1.0000000000000p-1022, 0x1.0000000000000p-1022, 0x1.0000000000000p-1022) == 0x1.0000000000000p-1022
+; run: %fma_f64(0x0.0, 0x0.0, 0x1.0000000000000p-1022) == 0x1.0000000000000p-1022
+
+; F64 Subnormals
+; run: %fma_f64(0x0.8000000000000p-1022, 0x0.8000000000000p-1022, 0x0.8000000000000p-1022) == 0x0.8000000000000p-1022
+; run: %fma_f64(0x0.8000000000000p-1022, 0x0.8000000000000p-1022, 0x0.0) == 0x0.0
+; run: %fma_f64(0x0.0, 0x0.0, 0x0.8000000000000p-1022) == 0x0.8000000000000p-1022
+; run: %fma_f64(0x0.0000000000001p-1022, 0x0.0000000000001p-1022, 0x0.0000000000001p-1022) == 0x0.0000000000001p-1022
+; run: %fma_f64(0x0.0000000000001p-1022, 0x0.0000000000001p-1022, 0x0.0) == 0x0.0
+; run: %fma_f64(0x0.0, 0x0.0, 0x0.0000000000001p-1022) == 0x0.0000000000001p-1022
+
+
+;; The IEEE754 Standard does not make a lot of guarantees about what
+;; comes out of NaN producing operations, we just check if its a NaN
+function %fma_is_nan_f64(f64, f64, f64) -> i32 {
+block0(v0: f64, v1: f64, v2: f64):
+    v3 = fma v0, v1, v2
+    v4 = fcmp ne v3, v3
+    v5 = bint.i32 v4
+    return v5
+}
+; run: %fma_is_nan_f64(Inf, -Inf, Inf) == 1
+; run: %fma_is_nan_f64(-Inf, Inf, Inf) == 1
+; run: %fma_is_nan_f64(-Inf, -Inf, -Inf) == 1
+
+; run: %fma_is_nan_f64(+NaN, 0x0.0, 0x0.0) == 1
+; run: %fma_is_nan_f64(0x0.0, +NaN, 0x0.0) == 1
+; run: %fma_is_nan_f64(0x0.0, 0x0.0, +NaN) == 1
+; run: %fma_is_nan_f64(-NaN, 0x0.0, 0x0.0) == 1
+; run: %fma_is_nan_f64(0x0.0, -NaN, 0x0.0) == 1
+; run: %fma_is_nan_f64(0x0.0, 0x0.0, -NaN) == 1
diff --git a/cranelift/filetests/filetests/runtests/fneg.clif b/cranelift/filetests/filetests/runtests/fneg.clif
new file mode 100644
index 000000000..bfe63f924
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/fneg.clif
@@ -0,0 +1,93 @@
+test interpret
+test run
+target aarch64
+target x86_64
+target s390x
+
+function %fneg_f32(f32) -> f32 {
+block0(v0: f32):
+    v1 = fneg v0
+    return v1
+}
+; run: %fneg_f32(0x9.0) == -0x9.0
+; run: %fneg_f32(-0x9.0) == 0x9.0
+; run: %fneg_f32(0x0.0) == -0x0.0
+; run: %fneg_f32(-0x0.0) == 0x0.0
+
+; F32 Inf
+; run: %fneg_f32(Inf) == -Inf
+; run: %fneg_f32(-Inf) == +Inf
+
+; F32 Epsilon  / Max / Min Positive
+; run: %fneg_f32(0x1.000000p-23) == -0x1.000000p-23
+; run: %fneg_f32(-0x1.000000p-23) == 0x1.000000p-23
+; run: %fneg_f32(0x1.fffffep127) == -0x1.fffffep127
+; run: %fneg_f32(-0x1.fffffep127) == 0x1.fffffep127
+; run: %fneg_f32(0x1.000000p-126) == -0x1.000000p-126
+; run: %fneg_f32(-0x1.000000p-126) == 0x1.000000p-126
+
+; F32 Subnormals
+; run: %fneg_f32(0x0.800000p-126) == -0x0.800000p-126
+; run: %fneg_f32(-0x0.800000p-126) == 0x0.800000p-126
+; run: %fneg_f32(0x0.000002p-126) == -0x0.000002p-126
+; run: %fneg_f32(-0x0.000002p-126) == 0x0.000002p-126
+
+; F32 NaN's
+; Unlike with other operations fneg is guaranteed to only affect the sign bit
+; run: %fneg_f32(+NaN) == -NaN
+; run: %fneg_f32(-NaN) == +NaN
+; run: %fneg_f32(+NaN:0x0) == -NaN:0x0
+; run: %fneg_f32(+NaN:0x1) == -NaN:0x1
+; run: %fneg_f32(+NaN:0x300001) == -NaN:0x300001
+; run: %fneg_f32(-NaN:0x0) == +NaN:0x0
+; run: %fneg_f32(-NaN:0x1) == +NaN:0x1
+; run: %fneg_f32(-NaN:0x300001) == +NaN:0x300001
+; run: %fneg_f32(+sNaN:0x1) == -sNaN:0x1
+; run: %fneg_f32(-sNaN:0x1) == +sNaN:0x1
+; run: %fneg_f32(+sNaN:0x200001) == -sNaN:0x200001
+; run: %fneg_f32(-sNaN:0x200001) == +sNaN:0x200001
+
+
+
+function %fneg_f64(f64) -> f64 {
+block0(v0: f64):
+    v1 = fneg v0
+    return v1
+}
+; run: %fneg_f64(0x9.0) == -0x9.0
+; run: %fneg_f64(-0x9.0) == 0x9.0
+; run: %fneg_f64(0x0.0) == -0x0.0
+; run: %fneg_f64(-0x0.0) == 0x0.0
+
+; F64 Inf
+; run: %fneg_f64(+Inf) == -Inf
+; run: %fneg_f64(-Inf) == +Inf
+
+; F64 Epsilon / Max / Min Positive
+; run: %fneg_f64(0x1.0000000000000p-52) == -0x1.0000000000000p-52
+; run: %fneg_f64(-0x1.0000000000000p-52) == 0x1.0000000000000p-52
+; run: %fneg_f64(0x1.fffffffffffffp1023) == -0x1.fffffffffffffp1023
+; run: %fneg_f64(-0x1.fffffffffffffp1023) == 0x1.fffffffffffffp1023
+; run: %fneg_f64(0x1.0000000000000p-1022) == -0x1.0000000000000p-1022
+; run: %fneg_f64(-0x1.0000000000000p-1022) == 0x1.0000000000000p-1022
+
+; F64 Subnormals
+; run: %fneg_f64(0x0.8000000000000p-1022) == -0x0.8000000000000p-1022
+; run: %fneg_f64(-0x0.8000000000000p-1022) == 0x0.8000000000000p-1022
+; run: %fneg_f64(0x0.0000000000001p-1022) == -0x0.0000000000001p-1022
+; run: %fneg_f64(-0x0.0000000000001p-1022) == 0x0.0000000000001p-1022
+
+; F64 NaN's
+; Unlike with other operations fneg is guaranteed to only affect the sign bit
+; run: %fneg_f64(+NaN) == -NaN
+; run: %fneg_f64(-NaN) == +NaN
+; run: %fneg_f64(+NaN:0x0) == -NaN:0x0
+; run: %fneg_f64(+NaN:0x1) == -NaN:0x1
+; run: %fneg_f64(+NaN:0x4000000000001) == -NaN:0x4000000000001
+; run: %fneg_f64(-NaN:0x0) == +NaN:0x0
+; run: %fneg_f64(-NaN:0x1) == +NaN:0x1
+; run: %fneg_f64(-NaN:0x4000000000001) == +NaN:0x4000000000001
+; run: %fneg_f64(+sNaN:0x1) == -sNaN:0x1
+; run: %fneg_f64(-sNaN:0x1) == +sNaN:0x1
+; run: %fneg_f64(+sNaN:0x4000000000001) == -sNaN:0x4000000000001
+; run: %fneg_f64(-sNaN:0x4000000000001) == +sNaN:0x4000000000001
diff --git a/cranelift/filetests/filetests/runtests/heap.clif b/cranelift/filetests/filetests/runtests/heap.clif
index 35d80a515..9b42070eb 100644
--- a/cranelift/filetests/filetests/runtests/heap.clif
+++ b/cranelift/filetests/filetests/runtests/heap.clif
@@ -1,10 +1,11 @@
+test interpret
 test run
 target x86_64
 target s390x
 target aarch64
 
 
-function %static_heap_i64_load_store(i64 vmctx, i64, i32) -> i32 {
+function %static_heap_i64(i64 vmctx, i64, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     heap0 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0, index_type i64
@@ -16,13 +17,13 @@ block0(v0: i64, v1: i64, v2: i32):
     return v4
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %static_heap_i64_load_store(0, 1) == 1
-; run: %static_heap_i64_load_store(0, -1) == -1
-; run: %static_heap_i64_load_store(16, 1) == 1
-; run: %static_heap_i64_load_store(16, -1) == -1
+; run: %static_heap_i64(0, 1) == 1
+; run: %static_heap_i64(0, -1) == -1
+; run: %static_heap_i64(16, 1) == 1
+; run: %static_heap_i64(16, -1) == -1
 
 
-function %static_heap_i32_load_store(i64 vmctx, i32, i32) -> i32 {
+function %static_heap_i32(i64 vmctx, i32, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     heap0 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0, index_type i32
@@ -34,13 +35,13 @@ block0(v0: i64, v1: i32, v2: i32):
     return v4
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %static_heap_i32_load_store(0, 1) == 1
-; run: %static_heap_i32_load_store(0, -1) == -1
-; run: %static_heap_i32_load_store(16, 1) == 1
-; run: %static_heap_i32_load_store(16, -1) == -1
+; run: %static_heap_i32(0, 1) == 1
+; run: %static_heap_i32(0, -1) == -1
+; run: %static_heap_i32(16, 1) == 1
+; run: %static_heap_i32(16, -1) == -1
 
 
-function %static_heap_i32_load_store_no_min(i64 vmctx, i32, i32) -> i32 {
+function %heap_no_min(i64 vmctx, i32, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     heap0 = static gv1, bound 0x1_0000_0000, offset_guard 0, index_type i32
@@ -52,13 +53,13 @@ block0(v0: i64, v1: i32, v2: i32):
     return v4
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %static_heap_i32_load_store_no_min(0, 1) == 1
-; run: %static_heap_i32_load_store_no_min(0, -1) == -1
-; run: %static_heap_i32_load_store_no_min(16, 1) == 1
-; run: %static_heap_i32_load_store_no_min(16, -1) == -1
+; run: %heap_no_min(0, 1) == 1
+; run: %heap_no_min(0, -1) == -1
+; run: %heap_no_min(16, 1) == 1
+; run: %heap_no_min(16, -1) == -1
 
 
-function %dynamic_heap_i64_load_store(i64 vmctx, i64, i32) -> i32 {
+function %dynamic_i64(i64 vmctx, i64, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     gv2 = load.i64 notrap aligned gv0+8
@@ -71,13 +72,13 @@ block0(v0: i64, v1: i64, v2: i32):
     return v4
 }
 ; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %dynamic_heap_i64_load_store(0, 1) == 1
-; run: %dynamic_heap_i64_load_store(0, -1) == -1
-; run: %dynamic_heap_i64_load_store(16, 1) == 1
-; run: %dynamic_heap_i64_load_store(16, -1) == -1
+; run: %dynamic_i64(0, 1) == 1
+; run: %dynamic_i64(0, -1) == -1
+; run: %dynamic_i64(16, 1) == 1
+; run: %dynamic_i64(16, -1) == -1
 
 
-function %dynamic_heap_i32_load_store(i64 vmctx, i32, i32) -> i32 {
+function %dynamic_i32(i64 vmctx, i32, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     gv2 = load.i64 notrap aligned gv0+8
@@ -90,13 +91,13 @@ block0(v0: i64, v1: i32, v2: i32):
     return v4
 }
 ; heap: dynamic, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %dynamic_heap_i32_load_store(0, 1) == 1
-; run: %dynamic_heap_i32_load_store(0, -1) == -1
-; run: %dynamic_heap_i32_load_store(16, 1) == 1
-; run: %dynamic_heap_i32_load_store(16, -1) == -1
+; run: %dynamic_i32(0, 1) == 1
+; run: %dynamic_i32(0, -1) == -1
+; run: %dynamic_i32(16, 1) == 1
+; run: %dynamic_i32(16, -1) == -1
 
 
-function %multi_heap_load_store(i64 vmctx, i32, i32) -> i32 {
+function %multi_load_store(i64 vmctx, i32, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     gv2 = load.i64 notrap aligned gv0+16
@@ -125,12 +126,47 @@ block0(v0: i64, v1: i32, v2: i32):
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
 ; heap: dynamic, size=0x1000, ptr=vmctx+16, bound=vmctx+24
-; run: %multi_heap_load_store(1, 2) == 3
-; run: %multi_heap_load_store(4, 5) == 9
+; run: %multi_load_store(1, 2) == 3
+; run: %multi_load_store(4, 5) == 9
 
 
 
-function %static_heap_i64_load_store_unaligned(i64 vmctx, i64, i32) -> i32 {
+; Uses multiple heaps, but heap0 refers to the second heap, and heap1 refers to the first heap
+; This is a regression test for the interpreter
+function %out_of_order(i64 vmctx, i32, i32) -> i32 {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned gv0+0
+    gv2 = load.i64 notrap aligned gv0+16
+    gv3 = load.i64 notrap aligned gv0+24
+    heap0 = dynamic gv2, bound gv3, offset_guard 0, index_type i32
+    heap1 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0, index_type i64
+
+block0(v0: i64, v1: i32, v2: i32):
+    v3 = iconst.i32 0
+    v4 = iconst.i64 0
+
+    ; Store lhs in heap0
+    v5 = heap_addr.i64 heap0, v3, 4
+    store.i32 v1, v5
+
+    ; Store rhs in heap1
+    v6 = heap_addr.i64 heap1, v4, 4
+    store.i32 v2, v6
+
+
+    v7 = load.i32 v5
+    v8 = load.i32 v6
+
+    v9 = iadd.i32 v7, v8
+    return v9
+}
+; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
+; heap: dynamic, size=0x1000, ptr=vmctx+16, bound=vmctx+24
+; run: %out_of_order(1, 2) == 3
+; run: %out_of_order(4, 5) == 9
+
+
+function %unaligned_access(i64 vmctx, i64, i32) -> i32 {
     gv0 = vmctx
     gv1 = load.i64 notrap aligned gv0+0
     heap0 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0, index_type i64
@@ -142,18 +178,18 @@ block0(v0: i64, v1: i64, v2: i32):
     return v4
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %static_heap_i64_load_store_unaligned(0, 1) == 1
-; run: %static_heap_i64_load_store_unaligned(0, -1) == -1
-; run: %static_heap_i64_load_store_unaligned(1, 1) == 1
-; run: %static_heap_i64_load_store_unaligned(1, -1) == -1
-; run: %static_heap_i64_load_store_unaligned(2, 1) == 1
-; run: %static_heap_i64_load_store_unaligned(2, -1) == -1
-; run: %static_heap_i64_load_store_unaligned(3, 1) == 1
-; run: %static_heap_i64_load_store_unaligned(3, -1) == -1
+; run: %unaligned_access(0, 1) == 1
+; run: %unaligned_access(0, -1) == -1
+; run: %unaligned_access(1, 1) == 1
+; run: %unaligned_access(1, -1) == -1
+; run: %unaligned_access(2, 1) == 1
+; run: %unaligned_access(2, -1) == -1
+; run: %unaligned_access(3, 1) == 1
+; run: %unaligned_access(3, -1) == -1
 
 
 ; This stores data in the place of the pointer in the vmctx struct, not in the heap itself.
-function %static_heap_i64_iadd_imm(i64 vmctx, i32) -> i32 {
+function %iadd_imm(i64 vmctx, i32) -> i32 {
     gv0 = vmctx
     gv1 = iadd_imm.i64 gv0, 0
     heap0 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0x8000_0000, index_type i64
@@ -166,5 +202,5 @@ block0(v0: i64, v1: i32):
     return v4
 }
 ; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
-; run: %static_heap_i64_iadd_imm(1) == 1
-; run: %static_heap_i64_iadd_imm(-1) == -1
+; run: %iadd_imm(1) == 1
+; run: %iadd_imm(-1) == -1
diff --git a/cranelift/filetests/filetests/runtests/simd-min-max.clif b/cranelift/filetests/filetests/runtests/simd-min-max.clif
new file mode 100644
index 000000000..54653616f
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/simd-min-max.clif
@@ -0,0 +1,107 @@
+test run
+target aarch64
+target x86_64
+
+function %imin_i8x16(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = imin v0, v1
+  return v2
+}
+
+; run: %imin_i8x16([0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f], [0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]) == [ 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f ]
+
+; run: %imin_i8x16([0x90 0x01 0x92 0x03 0x94 0x05 0x96 0x07 0x98 0x09 0x9a 0x0b 0x9c 0x0d 0x9e 0x0f], [0x10 0x91 0x12 0x93 0x14 0x95 0x16 0x97 0x18 0x99 0x1a 0x9b 0x1c 0x9d 0x1e 0x9f ]) == [ 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f ]
+
+function %imin_i16x8(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = imin v0, v1
+  return v2
+}
+
+; run: %imin_i16x8([0x1234 0x5678 0x9876 0x5432 0x7654 0x1234 0x4567 0x3456 ], [ 0x4567 0x1234 0x6789 0x0987 0x0123 0x3210 0x7890 0x3456 ]) == [ 0x1234 0x1234 0x9876 0x0987 0x0123 0x1234 0x4567 0x3456 ]
+
+function %imin_i32x4(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = imin v0, v1
+  return v2
+}
+
+; run: %imin_i32x4([0xBAADF00D 0xDEADBEEF 0xC00FFFEE 0xBADAB00F], [0xCA11ACAB 0x12349876 0x98763210 0x43216789]) == [ 0xBAADF00D 0xDEADBEEF 0x98763210 0xBADAB00F ]
+
+function %umin_i8x16(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = umin v0, v1
+  return v2
+}
+
+; run: %umin_i8x16([0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f], [0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]) == [ 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f ]
+
+; run: %umin_i8x16([0x90 0x01 0x92 0x03 0x94 0x05 0x96 0x07 0x98 0x09 0x9a 0x0b 0x9c 0x0d 0x9e 0x0f], [0x10 0x91 0x12 0x93 0x14 0x95 0x16 0x97 0x18 0x99 0x1a 0x9b 0x1c 0x9d 0x1e 0x9f ]) == [ 0x10 0x01 0x12 0x03 0x14 0x05 0x16 0x07 0x18 0x09 0x1a 0x0b 0x1c 0x0d 0x1e 0x0f ]
+
+function %umin_i16x8(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = umin v0, v1
+  return v2
+}
+
+; run: %umin_i16x8([0x1234 0x5678 0x9876 0x5432 0x7654 0x1234 0x4567 0x3456 ], [ 0x4567 0x1234 0x6789 0x0987 0x0123 0x3210 0x7890 0x3456 ]) == [ 0x1234 0x1234 0x6789 0x0987 0x0123 0x1234 0x4567 0x3456 ]
+
+function %umin_i32x4(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = umin v0, v1
+  return v2
+}
+
+; run: %umin_i32x4([0xBAADF00D 0xDEADBEEF 0xC00FFFEE 0xBADAB00F], [0xCA11ACAB 0x12349876 0x98763210 0x43216789]) == [ 0xBAADF00D 0x12349876 0x98763210 0x43216789 ]
+
+function %imax_i8x16(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = imax v0, v1
+  return v2
+}
+
+; run: %imax_i8x16([0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f], [0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]) == [ 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]
+
+; run: %imax_i8x16([0x90 0x01 0x92 0x03 0x94 0x05 0x96 0x07 0x98 0x09 0x9a 0x0b 0x9c 0x0d 0x9e 0x0f], [0x10 0x91 0x12 0x93 0x14 0x95 0x16 0x97 0x18 0x99 0x1a 0x9b 0x1c 0x9d 0x1e 0x9f ]) == [ 0x10 0x01 0x12 0x03 0x14 0x05 0x16 0x07 0x18 0x09 0x1a 0x0b 0x1c 0x0d 0x1e 0x0f ]
+
+function %imax_i16x8(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = imax v0, v1
+  return v2
+}
+
+; run: %imax_i16x8([0x1234 0x5678 0x9876 0x5432 0x7654 0x1234 0x4567 0x3456 ], [ 0x4567 0x1234 0x6789 0x0987 0x0123 0x3210 0x7890 0x3456 ]) == [ 0x4567 0x5678 0x6789 0x5432 0x7654 0x3210 0x7890 0x3456 ]
+
+function %imax_i32x4(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = imax v0, v1
+  return v2
+}
+
+; run: %imax_i32x4([0xBAADF00D 0xDEADBEEF 0xC00FFFEE 0xBADAB00F], [0xCA11ACAB 0x12349876 0x98763210 0x43216789]) == [ 0xCA11ACAB 0x12349876 0xC00FFFEE 0x43216789 ]
+
+function %umax_i8x16(i8x16, i8x16) -> i8x16 {
+block0(v0: i8x16, v1: i8x16):
+  v2 = umax v0, v1
+  return v2
+}
+
+; run: %umax_i8x16([0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f], [0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]) == [ 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f ]
+
+; run: %umax_i8x16([0x90 0x01 0x92 0x03 0x94 0x05 0x96 0x07 0x98 0x09 0x9a 0x0b 0x9c 0x0d 0x9e 0x0f], [0x10 0x91 0x12 0x93 0x14 0x95 0x16 0x97 0x18 0x99 0x1a 0x9b 0x1c 0x9d 0x1e 0x9f ]) == [ 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f ]
+
+function %umax_i16x8(i16x8, i16x8) -> i16x8 {
+block0(v0: i16x8, v1: i16x8):
+  v2 = umax v0, v1
+  return v2
+}
+
+; run: %umax_i16x8([0x1234 0x5678 0x9876 0x5432 0x7654 0x1234 0x4567 0x3456 ], [ 0x4567 0x1234 0x6789 0x0987 0x0123 0x3210 0x7890 0x3456 ]) == [ 0x4567 0x5678 0x9876 0x5432 0x7654 0x3210 0x7890 0x3456 ]
+
+function %umax_i32x4(i32x4, i32x4) -> i32x4 {
+block0(v0: i32x4, v1: i32x4):
+  v2 = umax v0, v1
+  return v2
+}
+
+; run: %umax_i32x4([0xBAADF00D 0xDEADBEEF 0xC00FFFEE 0xBADAB00F], [0xCA11ACAB 0x12349876 0x98763210 0x43216789]) == [ 0xCA11ACAB 0xDEADBEEF 0xC00FFFEE 0xBADAB00F ]
diff --git a/cranelift/filetests/src/runtest_environment.rs b/cranelift/filetests/src/runtest_environment.rs
index 7d528d129..5a7fe1564 100644
--- a/cranelift/filetests/src/runtest_environment.rs
+++ b/cranelift/filetests/src/runtest_environment.rs
@@ -1,6 +1,5 @@
 use anyhow::anyhow;
-use cranelift_codegen::data_value::DataValue;
-use cranelift_codegen::ir::Type;
+use cranelift_codegen::ir::{ArgumentPurpose, Function};
 use cranelift_reader::parse_heap_command;
 use cranelift_reader::{Comment, HeapCommand};
 
@@ -45,68 +44,37 @@ impl RuntestEnvironment {
         !self.heaps.is_empty()
     }
 
-    /// Allocates a struct to be injected into the test.
-    pub fn runtime_struct(&self) -> RuntestContext {
-        RuntestContext::new(&self)
-    }
-}
-
-type HeapMemory = Vec<u8>;
-
-/// A struct that provides info about the environment to the test
-#[derive(Debug, Clone)]
-pub struct RuntestContext {
-    /// Store the heap memory alongside the context info so that we don't accidentally deallocate
-    /// it too early.
-    #[allow(dead_code)]
-    heaps: Vec<HeapMemory>,
-
-    /// This is the actual struct that gets passed into the `vmctx`  argument of the tests.
-    /// It has a specific memory layout that all tests agree with.
-    ///
-    /// Currently we only have to store heap info, so we store the heap start and end addresses in
-    /// a 64 bit slot for each heap.
-    ///
-    /// ┌────────────┐
-    /// │heap0: start│
-    /// ├────────────┤
-    /// │heap0: end  │
-    /// ├────────────┤
-    /// │heap1: start│
-    /// ├────────────┤
-    /// │heap1: end  │
-    /// ├────────────┤
-    /// │etc...      │
-    /// └────────────┘
-    context_struct: Vec<u64>,
-}
-
-impl RuntestContext {
-    pub fn new(env: &RuntestEnvironment) -> Self {
-        let heaps: Vec<HeapMemory> = env
-            .heaps
+    /// Allocates memory for heaps
+    pub fn allocate_memory(&self) -> Vec<HeapMemory> {
+        self.heaps
             .iter()
             .map(|cmd| {
                 let size: u64 = cmd.size.into();
                 vec![0u8; size as usize]
             })
-            .collect();
+            .collect()
+    }
 
-        let context_struct = heaps
-            .iter()
-            .flat_map(|heap| [heap.as_ptr(), heap.as_ptr().wrapping_add(heap.len())])
-            .map(|p| p as usize as u64)
-            .collect();
+    /// Validates the signature of a [Function] ensuring that if this environment is active, the
+    /// function has a `vmctx` argument
+    pub fn validate_signature(&self, func: &Function) -> Result<(), String> {
+        let first_arg_is_vmctx = func
+            .signature
+            .params
+            .first()
+            .map(|p| p.purpose == ArgumentPurpose::VMContext)
+            .unwrap_or(false);
 
-        Self {
-            heaps,
-            context_struct,
+        if !first_arg_is_vmctx && self.is_active() {
+            return Err(concat!(
+                "This test requests a heap, but the first argument is not `i64 vmctx`.\n",
+                "See docs/testing.md for more info on using heap annotations."
+            )
+            .to_string());
         }
-    }
 
-    /// Creates a [DataValue] with a target isa pointer type to the context struct.
-    pub fn pointer(&self, ty: Type) -> DataValue {
-        let ptr = self.context_struct.as_ptr() as usize as i128;
-        DataValue::from_integer(ptr, ty).expect("Failed to cast pointer to native target size")
+        Ok(())
     }
 }
+
+pub(crate) type HeapMemory = Vec<u8>;
diff --git a/cranelift/filetests/src/test_interpret.rs b/cranelift/filetests/src/test_interpret.rs
index 7374d84c2..623297894 100644
--- a/cranelift/filetests/src/test_interpret.rs
+++ b/cranelift/filetests/src/test_interpret.rs
@@ -3,10 +3,13 @@
 //! The `interpret` test command interprets each function on the host machine
 //! using [RunCommand](cranelift_reader::RunCommand)s.
 
+use crate::runtest_environment::RuntestEnvironment;
 use crate::subtest::{Context, SubTest};
+use cranelift_codegen::data_value::DataValue;
+use cranelift_codegen::ir::types::I64;
 use cranelift_codegen::{self, ir};
 use cranelift_interpreter::environment::FunctionStore;
-use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
+use cranelift_interpreter::interpreter::{HeapInit, Interpreter, InterpreterState};
 use cranelift_interpreter::step::ControlFlow;
 use cranelift_reader::{parse_run_command, TestCommand};
 use log::trace;
@@ -36,6 +39,7 @@ impl SubTest for TestInterpret {
     }
 
     fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
+        let test_env = RuntestEnvironment::parse(&context.details.comments[..])?;
         for comment in context.details.comments.iter() {
             if let Some(command) = parse_run_command(comment.text, &func.signature)? {
                 trace!("Parsed run command: {}", command);
@@ -44,11 +48,21 @@ impl SubTest for TestInterpret {
                 env.add(func.name.to_string(), &func);
 
                 command
-                    .run(|func_name, args| {
+                    .run(|func_name, run_args| {
+                        test_env.validate_signature(&func)?;
+
+                        let mut state = InterpreterState::default().with_function_store(env);
+
+                        let mut args = Vec::with_capacity(run_args.len());
+                        if test_env.is_active() {
+                            let vmctx_addr = register_heaps(&mut state, &test_env);
+                            args.push(vmctx_addr);
+                        }
+                        args.extend_from_slice(run_args);
+
                         // Because we have stored function names with a leading %, we need to re-add it.
                         let func_name = &format!("%{}", func_name);
-                        let state = InterpreterState::default().with_function_store(env);
-                        match Interpreter::new(state).call_by_name(func_name, args) {
+                        match Interpreter::new(state).call_by_name(func_name, &args) {
                             Ok(ControlFlow::Return(results)) => Ok(results.to_vec()),
                             Ok(_) => {
                                 panic!("Unexpected returned control flow--this is likely a bug.")
@@ -62,3 +76,34 @@ impl SubTest for TestInterpret {
         Ok(())
     }
 }
+
+/// Build a VMContext struct with the layout described in docs/testing.md.
+pub fn register_heaps<'a>(
+    state: &mut InterpreterState<'a>,
+    test_env: &RuntestEnvironment,
+) -> DataValue {
+    let mem = test_env.allocate_memory();
+    let vmctx_struct = mem
+        .into_iter()
+        // This memory layout (a contiguous list of base + bound ptrs)
+        // is enforced by the RuntestEnvironment when parsing the heap
+        // directives. So we are safe to replicate that here.
+        .flat_map(|mem| {
+            let heap_len = mem.len() as u64;
+            let heap = state.register_heap(HeapInit::FromBacking(mem));
+            [
+                state.get_heap_address(I64, heap, 0).unwrap(),
+                state.get_heap_address(I64, heap, heap_len).unwrap(),
+            ]
+        })
+        .map(|addr| {
+            let mut mem = [0u8; 8];
+            addr.write_to_slice(&mut mem[..]);
+            mem
+        })
+        .flatten()
+        .collect();
+
+    let vmctx_heap = state.register_heap(HeapInit::FromBacking(vmctx_struct));
+    state.get_heap_address(I64, vmctx_heap, 0).unwrap()
+}
diff --git a/cranelift/filetests/src/test_run.rs b/cranelift/filetests/src/test_run.rs
index 25cc49a06..4f80050b1 100644
--- a/cranelift/filetests/src/test_run.rs
+++ b/cranelift/filetests/src/test_run.rs
@@ -3,10 +3,11 @@
 //! The `run` test command compiles each function on the host machine and executes it
 
 use crate::function_runner::SingleFunctionCompiler;
-use crate::runtest_environment::RuntestEnvironment;
+use crate::runtest_environment::{HeapMemory, RuntestEnvironment};
 use crate::subtest::{Context, SubTest};
+use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir;
-use cranelift_codegen::ir::ArgumentPurpose;
+use cranelift_codegen::ir::Type;
 use cranelift_reader::parse_run_command;
 use cranelift_reader::TestCommand;
 use log::trace;
@@ -64,25 +65,13 @@ impl SubTest for TestRun {
                 let compiled_fn = compiler.compile(func.clone().into_owned())?;
                 command
                     .run(|_, run_args| {
-                        let runtime_struct = test_env.runtime_struct();
-
-                        let first_arg_is_vmctx = func
-                            .signature
-                            .params
-                            .first()
-                            .map(|p| p.purpose == ArgumentPurpose::VMContext)
-                            .unwrap_or(false);
-
-                        if !first_arg_is_vmctx && test_env.is_active() {
-                            return Err(concat!(
-                                "This test requests a heap, but the first argument is not `i64 vmctx`.\n",
-                                "See docs/testing.md for more info on using heap annotations."
-                            ).to_string());
-                        }
+                        test_env.validate_signature(&func)?;
+                        let (_heaps, _ctx_struct, vmctx_ptr) =
+                            build_vmctx_struct(&test_env, context.isa.unwrap().pointer_type());
 
                         let mut args = Vec::with_capacity(run_args.len());
                         if test_env.is_active() {
-                            args.push(runtime_struct.pointer(context.isa.unwrap().pointer_type()));
+                            args.push(vmctx_ptr);
                         }
                         args.extend_from_slice(run_args);
 
@@ -94,3 +83,24 @@ impl SubTest for TestRun {
         Ok(())
     }
 }
+
+/// Build a VMContext struct with the layout described in docs/testing.md.
+pub fn build_vmctx_struct(
+    test_env: &RuntestEnvironment,
+    ptr_ty: Type,
+) -> (Vec<HeapMemory>, Vec<u64>, DataValue) {
+    let heaps = test_env.allocate_memory();
+
+    let context_struct: Vec<u64> = heaps
+        .iter()
+        .flat_map(|heap| [heap.as_ptr(), heap.as_ptr().wrapping_add(heap.len())])
+        .map(|p| p as usize as u64)
+        .collect();
+
+    let ptr = context_struct.as_ptr() as usize as i128;
+    let ptr_dv =
+        DataValue::from_integer(ptr, ptr_ty).expect("Failed to cast pointer to native target size");
+
+    // Return all these to make sure we don't deallocate the heaps too early
+    (heaps, context_struct, ptr_dv)
+}
diff --git a/crates/misc/component-macro-test/Cargo.toml b/crates/misc/component-macro-test/Cargo.toml
new file mode 100644
index 000000000..f613aaeb2
--- /dev/null
+++ b/crates/misc/component-macro-test/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "component-macro-test"
+authors = ["The Wasmtime Project Developers"]
+license = "Apache-2.0 WITH LLVM-exception"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0", features = ["full"] }
diff --git a/crates/misc/component-macro-test/src/lib.rs b/crates/misc/component-macro-test/src/lib.rs
new file mode 100644
index 000000000..59a62de38
--- /dev/null
+++ b/crates/misc/component-macro-test/src/lib.rs
@@ -0,0 +1,34 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::parse_macro_input;
+
+#[proc_macro_attribute]
+pub fn add_variants(
+    attr: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    expand_variants(
+        &parse_macro_input!(attr as syn::LitInt),
+        parse_macro_input!(item as syn::ItemEnum),
+    )
+    .unwrap_or_else(syn::Error::into_compile_error)
+    .into()
+}
+
+fn expand_variants(count: &syn::LitInt, mut ty: syn::ItemEnum) -> syn::Result<TokenStream> {
+    let count = count
+        .base10_digits()
+        .parse::<usize>()
+        .map_err(|_| syn::Error::new(count.span(), "expected unsigned integer"))?;
+
+    ty.variants = (0..count)
+        .map(|index| syn::Variant {
+            attrs: Vec::new(),
+            ident: syn::Ident::new(&format!("V{}", index), Span::call_site()),
+            fields: syn::Fields::Unit,
+            discriminant: None,
+        })
+        .collect();
+
+    Ok(quote!(#ty))
+}
diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml
index 694eeb71b..a4781940c 100644
--- a/crates/test-programs/Cargo.toml
+++ b/crates/test-programs/Cargo.toml
@@ -11,10 +11,10 @@ license = "Apache-2.0 WITH LLVM-exception"
 cfg-if = "1.0"
 
 [dev-dependencies]
-wasi-common = { path = "../wasi-common", version = "0.39.0" }
-wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.39.0" }
-wasmtime = { path = "../wasmtime", version = "0.39.0" }
-wasmtime-wasi = { path = "../wasi", version = "0.39.0", features = ["tokio"] }
+wasi-common = { path = "../wasi-common", version = "0.40.0" }
+wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.40.0" }
+wasmtime = { path = "../wasmtime", version = "0.40.0" }
+wasmtime-wasi = { path = "../wasi", version = "0.40.0", features = ["tokio"] }
 target-lexicon = "0.12.0"
 pretty_env_logger = "0.4.0"
 tempfile = "3.1.0"
diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs
index 9cb50554e..4fee2a480 100644
--- a/tests/all/component_model.rs
+++ b/tests/all/component_model.rs
@@ -4,6 +4,7 @@ use wasmtime::{AsContextMut, Config, Engine};
 
 mod func;
 mod import;
+mod instance;
 mod macros;
 mod nested;
 mod post_return;
diff --git a/tests/all/component_model/instance.rs b/tests/all/component_model/instance.rs
new file mode 100644
index 000000000..8d57886a2
--- /dev/null
+++ b/tests/all/component_model/instance.rs
@@ -0,0 +1,57 @@
+use anyhow::Result;
+use wasmtime::component::*;
+use wasmtime::{Module, Store};
+
+#[test]
+fn instance_exports() -> Result<()> {
+    let engine = super::engine();
+    let component = r#"
+        (component
+            (import "a" (instance $i))
+            (import "b" (instance $i2 (export "m" (core module))))
+
+            (alias export $i2 "m" (core module $m))
+
+            (component $c
+                (component $c
+                    (export "m" (core module $m))
+                )
+                (instance $c (instantiate $c))
+                (export "i" (instance $c))
+            )
+            (instance $c (instantiate $c))
+            (export "i" (instance $c))
+            (export "r" (instance $i))
+            (export "r2" (instance $i2))
+        )
+    "#;
+    let component = Component::new(&engine, component)?;
+    let mut store = Store::new(&engine, ());
+    let mut linker = Linker::new(&engine);
+    linker.instance("a")?;
+    linker
+        .instance("b")?
+        .module("m", &Module::new(&engine, "(module)")?)?;
+    let instance = linker.instantiate(&mut store, &component)?;
+
+    let mut exports = instance.exports(&mut store);
+    assert!(exports.instance("not an instance").is_none());
+    let mut i = exports.instance("r").unwrap();
+    assert!(i.func("x").is_none());
+    drop(i);
+    exports.root().instance("i").unwrap();
+    let mut i2 = exports.instance("r2").unwrap();
+    assert!(i2.func("m").is_none());
+    assert!(i2.module("m").is_some());
+    drop(i2);
+
+    exports
+        .instance("i")
+        .unwrap()
+        .instance("i")
+        .unwrap()
+        .module("m")
+        .unwrap();
+
+    Ok(())
+}
diff --git a/tests/all/component_model/macros.rs b/tests/all/component_model/macros.rs
index 71a1385b1..ca519d2f4 100644
--- a/tests/all/component_model/macros.rs
+++ b/tests/all/component_model/macros.rs
@@ -1,5 +1,6 @@
 use super::TypedFuncExt;
 use anyhow::Result;
+use component_macro_test::add_variants;
 use std::fmt::Write;
 use wasmtime::component::{Component, ComponentType, Lift, Linker, Lower};
 use wasmtime::Store;
@@ -475,5 +476,41 @@ fn enum_derive() -> Result<()> {
         .get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
         .is_err());
 
+    #[add_variants(257)]
+    #[derive(ComponentType, Lift, Lower, PartialEq, Eq, Debug, Copy, Clone)]
+    #[component(enum)]
+    enum Many {}
+
+    let component = Component::new(
+        &engine,
+        make_echo_component(
+            &format!(
+                r#"(type $Foo (enum {}))"#,
+                (0..257)
+                    .map(|index| format!(r#""V{}""#, index))
+                    .collect::<Vec<_>>()
+                    .join(" ")
+            ),
+            4,
+        ),
+    )?;
+    let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
+    let func = instance.get_typed_func::<(Many,), Many, _>(&mut store, "echo")?;
+
+    for &input in &[Many::V0, Many::V1, Many::V254, Many::V255, Many::V256] {
+        let output = func.call_and_post_return(&mut store, (input,))?;
+
+        assert_eq!(input, output);
+    }
+
+    // TODO: The following case takes forever (i.e. I gave up after 30 minutes) to compile; we'll need to profile
+    // the compiler to find out why, which may point the way to a more efficient option.  On the other hand, this
+    // may not be worth spending time on.  Enums with over 2^16 variants are rare enough.
+
+    // #[add_variants(65537)]
+    // #[derive(ComponentType, Lift, Lower, PartialEq, Eq, Debug, Copy, Clone)]
+    // #[component(enum)]
+    // enum ManyMore {}
+
     Ok(())
 }
diff --git a/tests/misc_testsuite/component-model/instance.wast b/tests/misc_testsuite/component-model/instance.wast
index 071523f0a..3d68d272c 100644
--- a/tests/misc_testsuite/component-model/instance.wast
+++ b/tests/misc_testsuite/component-model/instance.wast
@@ -235,3 +235,20 @@
 
   (core instance (instantiate $verify (with "host" (instance $i))))
 )
+
+;; export an instance
+(component
+  (core module $m)
+  (instance $i (export "m" (core module $m)))
+  (export "i" (instance $i))
+)
+(component
+  (component $c)
+  (instance $i (instantiate $c))
+  (export "i" (instance $i))
+)
+(component
+  (import "host" (instance $i))
+  (export "i" (instance $i))
+)
+