Repository: bytecodealliance/wasmtime
Pull request #8156 (2024-03-16 13:45:28)

winch: Correctly handle the result of memory grow
This commit correctly handles the  result of the memory grow builtin function. Previously, it was assumed that the result of memory grow must be of the the target's pointer type, which doesn't accurately represent the address space covered by the memory type. 

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


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

cc @saulecabrera

<details>
This issue or pull request has been labeled: "winch"

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

* saulecabrera: winch

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

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

Patch:
diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml
index 0f4b3bcb8..4b7486336 100644
--- a/.github/workflows/cargo-audit.yml
+++ b/.github/workflows/cargo-audit.yml
@@ -10,6 +10,6 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions-rs/audit-check@v1
+      - uses: rustsec/audit-check@v1.4.1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index eb48f0ad9..f671b6176 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -65,7 +65,11 @@ jobs:
     - uses: actions/checkout@v4
       with:
         submodules: true
-    - run: git ls-files '*.h' '*.c' '*.cpp' | grep -v wasmtime-platform.h | xargs clang-format-15 --dry-run --Werror --verbose
+    - run: |
+        git ls-files '*.h' '*.c' '*.cpp' | \
+          grep -v wasmtime-platform.h | \
+          grep -v wasm.h | \
+          xargs clang-format-15 --dry-run --Werror --verbose
 
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
@@ -336,7 +340,7 @@ jobs:
     # Check whether `wasmtime` cross-compiles to aarch64-linux-android
     - run: rustup target add aarch64-linux-android
     - name: Set up JDK 17
-      uses: actions/setup-java@v3
+      uses: actions/setup-java@v4
       with:
         java-version: '17'
         distribution: 'temurin'
@@ -360,6 +364,11 @@ jobs:
       run: ./ci/vendor-wit.sh
     - run: git diff --exit-code
 
+    # Re-vendor the C API and make sure it's up-to-date.
+    - name: Re-vendor WIT
+      run: ./ci/vendor-c-api-headers.sh
+    - run: git diff --exit-code
+
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
       if: failure() && github.event_name != 'pull_request'
diff --git a/.gitmodules b/.gitmodules
index bef1a49be..dc9dfa50f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
 [submodule "tests/spec_testsuite"]
 	path = tests/spec_testsuite
 	url = https://github.com/WebAssembly/testsuite
-[submodule "crates/c-api/wasm-c-api"]
-	path = crates/c-api/wasm-c-api
-	url = https://github.com/WebAssembly/wasm-c-api
 [submodule "crates/wasi-nn/spec"]
 	path = crates/wasi-nn/spec
 	url = https://github.com/WebAssembly/wasi-nn
diff --git a/RELEASES.md b/RELEASES.md
index 30e58bfdc..52b80b5b5 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -8,11 +8,15 @@ Unreleased.
 
 ### Changed
 
+* The `-S common` flag is renamed to `-S cli`, to better reflect that it provides
+  the wasi-cli APIs. `-S common` is still accepted for now, and will be deprecated
+  in the future.
+
 --------------------------------------------------------------------------------
 
 ## 19.0.0
 
-Unreleased.
+Released 2024-03-20
 
 ### Added
 
diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh
index d8a249f11..114174bfe 100755
--- a/ci/build-tarballs.sh
+++ b/ci/build-tarballs.sh
@@ -41,7 +41,6 @@ mkdir tmp/$bin_pkgname
 cp LICENSE README.md tmp/$api_pkgname
 cp LICENSE README.md tmp/$bin_pkgname
 cp -r crates/c-api/include tmp/$api_pkgname
-cp crates/c-api/wasm-c-api/include/wasm.h tmp/$api_pkgname/include
 
 # For *-min builds rename artifacts with a `-min` suffix to avoid eventual
 # clashes with the normal builds when the tarballs are unioned together.
diff --git a/ci/vendor-c-api-headers.sh b/ci/vendor-c-api-headers.sh
new file mode 100755
index 000000000..fa0b41792
--- /dev/null
+++ b/ci/vendor-c-api-headers.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Script to re-vendor the headers from the `wasm-c-api` proposal.
+
+rev="2ce1367c9d1271c83fb63bef26d896a2f290cd23"
+files="wasm.h wasm.hh"
+
+set -ex
+
+for file in $files; do
+  dst=crates/c-api/include/$file
+  pretty_url=https://github.com/WebAssembly/wasm-c-api/blob/$rev/include/$file
+  url=https://raw.githubusercontent.com/WebAssembly/wasm-c-api/$rev/include/$file
+  cat >$dst <<-EOF
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <$pretty_url>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+EOF
+  curl $url >> $dst
+done
diff --git a/cranelift/codegen/src/alias_analysis.rs b/cranelift/codegen/src/alias_analysis.rs
index f3b6339f9..f33f6eebf 100644
--- a/cranelift/codegen/src/alias_analysis.rs
+++ b/cranelift/codegen/src/alias_analysis.rs
@@ -68,7 +68,7 @@ use crate::{
     inst_predicates::{
         has_memory_fence_semantics, inst_addr_offset_type, inst_store_data, visit_block_succs,
     },
-    ir::{immediates::Offset32, Block, Function, Inst, Opcode, Type, Value},
+    ir::{immediates::Offset32, AliasRegion, Block, Function, Inst, Opcode, Type, Value},
     trace,
 };
 use cranelift_entity::{packed_option::PackedOption, EntityRef};
@@ -93,14 +93,11 @@ impl LastStores {
             self.other = inst.into();
         } else if opcode.can_store() {
             if let Some(memflags) = func.dfg.insts[inst].memflags() {
-                if memflags.heap() {
-                    self.heap = inst.into();
-                } else if memflags.table() {
-                    self.table = inst.into();
-                } else if memflags.vmctx() {
-                    self.vmctx = inst.into();
-                } else {
-                    self.other = inst.into();
+                match memflags.alias_region() {
+                    None => self.other = inst.into(),
+                    Some(AliasRegion::Heap) => self.heap = inst.into(),
+                    Some(AliasRegion::Table) => self.table = inst.into(),
+                    Some(AliasRegion::Vmctx) => self.vmctx = inst.into(),
                 }
             } else {
                 self.heap = inst.into();
@@ -113,14 +110,11 @@ impl LastStores {
 
     fn get_last_store(&self, func: &Function, inst: Inst) -> PackedOption<Inst> {
         if let Some(memflags) = func.dfg.insts[inst].memflags() {
-            if memflags.heap() {
-                self.heap
-            } else if memflags.table() {
-                self.table
-            } else if memflags.vmctx() {
-                self.vmctx
-            } else {
-                self.other
+            match memflags.alias_region() {
+                None => self.other,
+                Some(AliasRegion::Heap) => self.heap,
+                Some(AliasRegion::Table) => self.table,
+                Some(AliasRegion::Vmctx) => self.vmctx,
             }
         } else if func.dfg.insts[inst].opcode().can_load()
             || func.dfg.insts[inst].opcode().can_store()
diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs
index cc9719e5f..c5efafaee 100644
--- a/cranelift/codegen/src/context.rs
+++ b/cranelift/codegen/src/context.rs
@@ -365,6 +365,7 @@ impl Context {
             "About to optimize with egraph phase:\n{}",
             self.func.display()
         );
+        let fisa = fisa.into();
         self.compute_loop_analysis();
         let mut alias_analysis = AliasAnalysis::new(&self.func, &self.domtree);
         let mut pass = EgraphPass::new(
@@ -372,6 +373,7 @@ impl Context {
             &self.domtree,
             &self.loop_analysis,
             &mut alias_analysis,
+            &fisa.flags,
             ctrl_plane,
         );
         pass.run();
diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs
index 8fa09e522..b1be475ac 100644
--- a/cranelift/codegen/src/egraph.rs
+++ b/cranelift/codegen/src/egraph.rs
@@ -7,12 +7,15 @@ use crate::dominator_tree::{DominatorTree, DominatorTreePreorder};
 use crate::egraph::elaborate::Elaborator;
 use crate::fx::FxHashSet;
 use crate::inst_predicates::{is_mergeable_for_egraph, is_pure_for_egraph};
+use crate::ir::pcc::Fact;
 use crate::ir::{
-    Block, DataFlowGraph, Function, Inst, InstructionData, Type, Value, ValueDef, ValueListPool,
+    Block, DataFlowGraph, Function, Inst, InstructionData, Opcode, Type, Value, ValueDef,
+    ValueListPool,
 };
 use crate::loop_analysis::LoopAnalysis;
 use crate::opts::IsleContext;
 use crate::scoped_hash_map::{Entry as ScopedEntry, ScopedHashMap};
+use crate::settings::Flags;
 use crate::trace;
 use crate::unionfind::UnionFind;
 use core::cmp::Ordering;
@@ -55,6 +58,8 @@ pub struct EgraphPass<'a> {
     /// Loop analysis results, used for built-in LICM during
     /// elaboration.
     loop_analysis: &'a LoopAnalysis,
+    /// Compiler flags.
+    flags: &'a Flags,
     /// Chaos-mode control-plane so we can test that we still get
     /// correct results when our heuristics make bad decisions.
     ctrl_plane: &'a mut ControlPlane,
@@ -91,6 +96,7 @@ where
     domtree: &'opt DominatorTreePreorder,
     pub(crate) alias_analysis: &'opt mut AliasAnalysis<'analysis>,
     pub(crate) alias_analysis_state: &'opt mut LastStores,
+    flags: &'opt Flags,
     ctrl_plane: &'opt mut ControlPlane,
     // Held locally during optimization of one node (recursively):
     pub(crate) rewrite_depth: usize,
@@ -203,6 +209,8 @@ where
                 }
             };
 
+            self.attach_constant_fact(inst, result);
+
             self.available_block[result] = self.get_available_block(inst);
             let opt_value = self.optimize_pure_enode(inst);
 
@@ -473,6 +481,22 @@ where
             false
         }
     }
+
+    /// Helper to propagate facts on constant values: if PCC is
+    /// enabled, then unconditionally add a fact attesting to the
+    /// Value's concrete value.
+    fn attach_constant_fact(&mut self, inst: Inst, value: Value) {
+        if self.flags.enable_pcc() {
+            if let InstructionData::UnaryImm {
+                opcode: Opcode::Iconst,
+                imm,
+            } = self.func.dfg.insts[inst]
+            {
+                let imm: i64 = imm.into();
+                self.func.dfg.facts[value] = Some(Fact::constant(64, imm as u64));
+            }
+        }
+    }
 }
 
 impl<'a> EgraphPass<'a> {
@@ -482,6 +506,7 @@ impl<'a> EgraphPass<'a> {
         raw_domtree: &'a DominatorTree,
         loop_analysis: &'a LoopAnalysis,
         alias_analysis: &'a mut AliasAnalysis<'a>,
+        flags: &'a Flags,
         ctrl_plane: &'a mut ControlPlane,
     ) -> Self {
         let num_values = func.dfg.num_values();
@@ -492,6 +517,7 @@ impl<'a> EgraphPass<'a> {
             domtree,
             loop_analysis,
             alias_analysis,
+            flags,
             ctrl_plane,
             stats: Stats::default(),
             eclasses: UnionFind::with_capacity(num_values),
@@ -662,6 +688,7 @@ impl<'a> EgraphPass<'a> {
                             domtree: &self.domtree,
                             alias_analysis: self.alias_analysis,
                             alias_analysis_state: &mut alias_analysis_state,
+                            flags: self.flags,
                             ctrl_plane: self.ctrl_plane,
                             optimized_values: Default::default(),
                         };
diff --git a/cranelift/codegen/src/ir/memflags.rs b/cranelift/codegen/src/ir/memflags.rs
index 8a5626d74..d9066f13a 100644
--- a/cranelift/codegen/src/ir/memflags.rs
+++ b/cranelift/codegen/src/ir/memflags.rs
@@ -2,57 +2,11 @@
 
 use super::TrapCode;
 use core::fmt;
+use core::str::FromStr;
 
 #[cfg(feature = "enable-serde")]
 use serde_derive::{Deserialize, Serialize};
 
-enum FlagBit {
-    /// Guaranteed not to trap. This may enable additional
-    /// optimizations to be performed.
-    Notrap,
-    /// If this memory access traps, use [TrapCode::TableOutOfBounds]
-    /// instead of [TrapCode::HeapOutOfBounds].
-    TableTrap,
-    /// Guaranteed to use "natural alignment" for the given type. This
-    /// may enable better instruction selection.
-    Aligned,
-    /// A load that reads data in memory that does not change for the
-    /// duration of the function's execution. This may enable
-    /// additional optimizations to be performed.
-    Readonly,
-    /// Load multi-byte values from memory in a little-endian format.
-    LittleEndian,
-    /// Load multi-byte values from memory in a big-endian format.
-    BigEndian,
-    /// Accesses only the "heap" part of abstract state. Used for
-    /// alias analysis. Mutually exclusive with "table" and "vmctx".
-    Heap,
-    /// Accesses only the "table" part of abstract state. Used for
-    /// alias analysis. Mutually exclusive with "heap" and "vmctx".
-    Table,
-    /// Accesses only the "vmctx" part of abstract state. Used for
-    /// alias analysis. Mutually exclusive with "heap" and "table".
-    Vmctx,
-    /// Check this load or store for safety when using the
-    /// proof-carrying-code framework. The address must have a
-    /// `PointsTo` fact attached with a sufficiently large valid range
-    /// for the accessed size.
-    Checked,
-}
-
-const NAMES: [&str; 10] = [
-    "notrap",
-    "tabletrap",
-    "aligned",
-    "readonly",
-    "little",
-    "big",
-    "heap",
-    "table",
-    "vmctx",
-    "checked",
-];
-
 /// Endianness of a memory access.
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
 pub enum Endianness {
@@ -62,6 +16,16 @@ pub enum Endianness {
     Big,
 }
 
+/// Which disjoint region of aliasing memory is accessed in this memory
+/// operation.
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+#[allow(missing_docs)]
+pub enum AliasRegion {
+    Heap,
+    Table,
+    Vmctx,
+}
+
 /// Flags for memory operations like load/store.
 ///
 /// Each of these flags introduce a limited form of undefined behavior. The flags each enable
@@ -75,9 +39,56 @@ pub enum Endianness {
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
 pub struct MemFlags {
+    // Initialized to all zeros to have all flags have their default value.
+    // This is interpreted through various methods below. Currently the bits of
+    // this are defined as:
+    //
+    // * 0 - aligned flag
+    // * 1 - readonly flag
+    // * 2 - little endian flag
+    // * 3 - big endian flag
+    // * 4 - checked flag
+    // * 5/6 - alias region
+    // * 7/8/9/10 - trap code
+    // * 11/12/13/14/15 - unallocated
+    //
+    // Current properties upheld are:
+    //
+    // * only one of little/big endian is set
+    // * only one alias region can be set - once set it cannot be changed
     bits: u16,
 }
 
+/// Guaranteed to use "natural alignment" for the given type. This
+/// may enable better instruction selection.
+const BIT_ALIGNED: u16 = 1 << 0;
+
+/// A load that reads data in memory that does not change for the
+/// duration of the function's execution. This may enable
+/// additional optimizations to be performed.
+const BIT_READONLY: u16 = 1 << 1;
+
+/// Load multi-byte values from memory in a little-endian format.
+const BIT_LITTLE_ENDIAN: u16 = 1 << 2;
+
+/// Load multi-byte values from memory in a big-endian format.
+const BIT_BIG_ENDIAN: u16 = 1 << 3;
+
+/// Check this load or store for safety when using the
+/// proof-carrying-code framework. The address must have a
+/// `PointsTo` fact attached with a sufficiently large valid range
+/// for the accessed size.
+const BIT_CHECKED: u16 = 1 << 4;
+
+/// Used for alias analysis, indicates which disjoint part of the abstract state
+/// is being accessed.
+const MASK_ALIAS_REGION: u16 = 0b11 << ALIAS_REGION_OFFSET;
+const ALIAS_REGION_OFFSET: u16 = 5;
+
+/// Trap code, if any, for this memory operation.
+const MASK_TRAP_CODE: u16 = 0b1111 << TRAP_CODE_OFFSET;
+const TRAP_CODE_OFFSET: u16 = 7;
+
 impl MemFlags {
     /// Create a new empty set of flags.
     pub const fn new() -> Self {
@@ -91,35 +102,100 @@ impl MemFlags {
     }
 
     /// Read a flag bit.
-    const fn read(self, bit: FlagBit) -> bool {
-        self.bits & (1 << bit as usize) != 0
+    const fn read_bit(self, bit: u16) -> bool {
+        self.bits & bit != 0
     }
 
     /// Return a new `MemFlags` with this flag bit set.
-    const fn with(mut self, bit: FlagBit) -> Self {
-        self.bits |= 1 << bit as usize;
+    const fn with_bit(mut self, bit: u16) -> Self {
+        self.bits |= bit;
+        self
+    }
+
+    /// Reads the alias region that this memory operation works with.
+    pub const fn alias_region(self) -> Option<AliasRegion> {
+        // NB: keep in sync with `with_alias_region`
+        match (self.bits & MASK_ALIAS_REGION) >> ALIAS_REGION_OFFSET {
+            0b00 => None,
+            0b01 => Some(AliasRegion::Heap),
+            0b10 => Some(AliasRegion::Table),
+            0b11 => Some(AliasRegion::Vmctx),
+            _ => unreachable!(),
+        }
+    }
+
+    /// Sets the alias region that this works on to the specified `region`.
+    pub const fn with_alias_region(mut self, region: Option<AliasRegion>) -> Self {
+        // NB: keep in sync with `alias_region`
+        let bits = match region {
+            None => 0b00,
+            Some(AliasRegion::Heap) => 0b01,
+            Some(AliasRegion::Table) => 0b10,
+            Some(AliasRegion::Vmctx) => 0b11,
+        };
+        self.bits &= !MASK_ALIAS_REGION;
+        self.bits |= bits << ALIAS_REGION_OFFSET;
         self
     }
 
+    /// Sets the alias region that this works on to the specified `region`.
+    pub fn set_alias_region(&mut self, region: Option<AliasRegion>) {
+        *self = self.with_alias_region(region);
+    }
+
     /// Set a flag bit by name.
     ///
-    /// Returns true if the flag was found and set, false for an unknown flag name.
-    /// Will also return false when trying to set inconsistent endianness flags.
-    pub fn set_by_name(&mut self, name: &str) -> bool {
-        match NAMES.iter().position(|&s| s == name) {
-            Some(bit) => {
-                let bits = self.bits | 1 << bit;
-                if (bits & (1 << FlagBit::LittleEndian as usize)) != 0
-                    && (bits & (1 << FlagBit::BigEndian as usize)) != 0
-                {
-                    false
-                } else {
-                    self.bits = bits;
-                    true
+    /// Returns true if the flag was found and set, false for an unknown flag
+    /// name.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error message if the `name` is known but couldn't be applied
+    /// due to it being a semantic error.
+    pub fn set_by_name(&mut self, name: &str) -> Result<bool, &'static str> {
+        *self = match name {
+            "notrap" => self.with_trap_code(None),
+            "aligned" => self.with_aligned(),
+            "readonly" => self.with_readonly(),
+            "little" => {
+                if self.read_bit(BIT_BIG_ENDIAN) {
+                    return Err("cannot set both big and little endian bits");
                 }
+                self.with_endianness(Endianness::Little)
             }
-            None => false,
-        }
+            "big" => {
+                if self.read_bit(BIT_LITTLE_ENDIAN) {
+                    return Err("cannot set both big and little endian bits");
+                }
+                self.with_endianness(Endianness::Big)
+            }
+            "heap" => {
+                if self.alias_region().is_some() {
+                    return Err("cannot set more than one alias region");
+                }
+                self.with_alias_region(Some(AliasRegion::Heap))
+            }
+            "table" => {
+                if self.alias_region().is_some() {
+                    return Err("cannot set more than one alias region");
+                }
+                self.with_alias_region(Some(AliasRegion::Table))
+            }
+            "vmctx" => {
+                if self.alias_region().is_some() {
+                    return Err("cannot set more than one alias region");
+                }
+                self.with_alias_region(Some(AliasRegion::Vmctx))
+            }
+            "checked" => self.with_checked(),
+
+            other => match TrapCode::from_str(other) {
+                Ok(TrapCode::User(_)) => return Err("cannot set user trap code on mem flags"),
+                Ok(code) => self.with_trap_code(Some(code)),
+                Err(()) => return Ok(false),
+            },
+        };
+        Ok(true)
     }
 
     /// Return endianness of the memory access.  This will return the endianness
@@ -128,9 +204,9 @@ impl MemFlags {
     /// caller since it is not explicitly encoded in CLIF IR -- this allows a
     /// front end to create IR without having to know the target endianness.
     pub const fn endianness(self, native_endianness: Endianness) -> Endianness {
-        if self.read(FlagBit::LittleEndian) {
+        if self.read_bit(BIT_LITTLE_ENDIAN) {
             Endianness::Little
-        } else if self.read(FlagBit::BigEndian) {
+        } else if self.read_bit(BIT_BIG_ENDIAN) {
             Endianness::Big
         } else {
             native_endianness
@@ -145,53 +221,36 @@ impl MemFlags {
     /// Set endianness of the memory access, returning new flags.
     pub const fn with_endianness(self, endianness: Endianness) -> Self {
         let res = match endianness {
-            Endianness::Little => self.with(FlagBit::LittleEndian),
-            Endianness::Big => self.with(FlagBit::BigEndian),
+            Endianness::Little => self.with_bit(BIT_LITTLE_ENDIAN),
+            Endianness::Big => self.with_bit(BIT_BIG_ENDIAN),
         };
-        assert!(!(res.read(FlagBit::LittleEndian) && res.read(FlagBit::BigEndian)));
+        assert!(!(res.read_bit(BIT_LITTLE_ENDIAN) && res.read_bit(BIT_BIG_ENDIAN)));
         res
     }
 
-    /// Test if the `notrap` flag is set.
+    /// Test if this memory operation cannot trap.
     ///
-    /// Normally, trapping is part of the semantics of a load/store operation. If the platform
-    /// would cause a trap when accessing the effective address, the Cranelift memory operation is
-    /// also required to trap.
+    /// By default `MemFlags` will assume that any load/store can trap and is
+    /// associated with a `TrapCode::HeapOutOfBounds` code. If the trap code is
+    /// configured to `None` though then this method will return `true` and
+    /// indicates that the memory operation will not trap.
     ///
-    /// The `notrap` flag tells Cranelift that the memory is *accessible*, which means that
-    /// accesses will not trap. This makes it possible to delete an unused load or a dead store
-    /// instruction.
+    /// If this returns `true` then the memory is *accessible*, which means
+    /// that accesses will not trap. This makes it possible to delete an unused
+    /// load or a dead store instruction.
     pub const fn notrap(self) -> bool {
-        self.read(FlagBit::Notrap)
+        self.trap_code().is_none()
     }
 
-    /// Set the `notrap` flag.
+    /// Sets the trap code for this `MemFlags` to `None`.
     pub fn set_notrap(&mut self) {
         *self = self.with_notrap();
     }
 
-    /// Set the `notrap` flag, returning new flags.
+    /// Sets the trap code for this `MemFlags` to `None`, returning the new
+    /// flags.
     pub const fn with_notrap(self) -> Self {
-        self.with(FlagBit::Notrap)
-    }
-
-    /// Test if the `tabletrap` bit is set.
-    ///
-    /// By default, if this memory access traps, the associated trap
-    /// code will be `HeapOutOfBounds`. With this flag set, the trap
-    /// code will instead be `TableOutOfBounds`.
-    pub const fn tabletrap(self) -> bool {
-        self.read(FlagBit::TableTrap)
-    }
-
-    /// Set the `tabletrap` bit.
-    pub fn set_tabletrap(&mut self) {
-        *self = self.with_tabletrap();
-    }
-
-    /// Set the `tabletrap` bit, returning new flags.
-    pub const fn with_tabletrap(self) -> Self {
-        self.with(FlagBit::TableTrap)
+        self.with_trap_code(None)
     }
 
     /// Test if the `aligned` flag is set.
@@ -200,7 +259,7 @@ impl MemFlags {
     /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the
     /// effective address is misaligned.
     pub const fn aligned(self) -> bool {
-        self.read(FlagBit::Aligned)
+        self.read_bit(BIT_ALIGNED)
     }
 
     /// Set the `aligned` flag.
@@ -210,7 +269,7 @@ impl MemFlags {
 
     /// Set the `aligned` flag, returning new flags.
     pub const fn with_aligned(self) -> Self {
-        self.with(FlagBit::Aligned)
+        self.with_bit(BIT_ALIGNED)
     }
 
     /// Test if the `readonly` flag is set.
@@ -219,7 +278,7 @@ impl MemFlags {
     /// This results in undefined behavior if the dereferenced memory is mutated at any time
     /// between when the function is called and when it is exited.
     pub const fn readonly(self) -> bool {
-        self.read(FlagBit::Readonly)
+        self.read_bit(BIT_READONLY)
     }
 
     /// Set the `readonly` flag.
@@ -229,79 +288,7 @@ impl MemFlags {
 
     /// Set the `readonly` flag, returning new flags.
     pub const fn with_readonly(self) -> Self {
-        self.with(FlagBit::Readonly)
-    }
-
-    /// Test if the `heap` bit is set.
-    ///
-    /// Loads and stores with this flag accesses the "heap" part of
-    /// abstract state. This is disjoint from the "table", "vmctx",
-    /// and "other" parts of abstract state. In concrete terms, this
-    /// means that behavior is undefined if the same memory is also
-    /// accessed by another load/store with one of the other
-    /// alias-analysis bits (`table`, `vmctx`) set, or `heap` not set.
-    pub const fn heap(self) -> bool {
-        self.read(FlagBit::Heap)
-    }
-
-    /// Set the `heap` bit. See the notes about mutual exclusion with
-    /// other bits in `heap()`.
-    pub fn set_heap(&mut self) {
-        *self = self.with_heap();
-    }
-
-    /// Set the `heap` bit, returning new flags.
-    pub const fn with_heap(self) -> Self {
-        assert!(!self.table() && !self.vmctx());
-        self.with(FlagBit::Heap)
-    }
-
-    /// Test if the `table` bit is set.
-    ///
-    /// Loads and stores with this flag accesses the "table" part of
-    /// abstract state. This is disjoint from the "heap", "vmctx",
-    /// and "other" parts of abstract state. In concrete terms, this
-    /// means that behavior is undefined if the same memory is also
-    /// accessed by another load/store with one of the other
-    /// alias-analysis bits (`heap`, `vmctx`) set, or `table` not set.
-    pub const fn table(self) -> bool {
-        self.read(FlagBit::Table)
-    }
-
-    /// Set the `table` bit. See the notes about mutual exclusion with
-    /// other bits in `table()`.
-    pub fn set_table(&mut self) {
-        *self = self.with_table();
-    }
-
-    /// Set the `table` bit, returning new flags.
-    pub const fn with_table(self) -> Self {
-        assert!(!self.heap() && !self.vmctx());
-        self.with(FlagBit::Table)
-    }
-
-    /// Test if the `vmctx` bit is set.
-    ///
-    /// Loads and stores with this flag accesses the "vmctx" part of
-    /// abstract state. This is disjoint from the "heap", "table",
-    /// and "other" parts of abstract state. In concrete terms, this
-    /// means that behavior is undefined if the same memory is also
-    /// accessed by another load/store with one of the other
-    /// alias-analysis bits (`heap`, `table`) set, or `vmctx` not set.
-    pub const fn vmctx(self) -> bool {
-        self.read(FlagBit::Vmctx)
-    }
-
-    /// Set the `vmctx` bit. See the notes about mutual exclusion with
-    /// other bits in `vmctx()`.
-    pub fn set_vmctx(&mut self) {
-        *self = self.with_vmctx();
-    }
-
-    /// Set the `vmctx` bit, returning new flags.
-    pub const fn with_vmctx(self) -> Self {
-        assert!(!self.heap() && !self.table());
-        self.with(FlagBit::Vmctx)
+        self.with_bit(BIT_READONLY)
     }
 
     /// Test if the `checked` bit is set.
@@ -316,7 +303,7 @@ impl MemFlags {
     /// checker's correctness) to access valid memory. This can be
     /// used to ensure memory safety and sandboxing.
     pub const fn checked(self) -> bool {
-        self.read(FlagBit::Checked)
+        self.read_bit(BIT_CHECKED)
     }
 
     /// Set the `checked` bit.
@@ -326,32 +313,139 @@ impl MemFlags {
 
     /// Set the `checked` bit, returning new flags.
     pub const fn with_checked(self) -> Self {
-        self.with(FlagBit::Checked)
+        self.with_bit(BIT_CHECKED)
     }
 
-    /// Get the trap code to report if this memory access traps. If the
-    /// `notrap` flag is set, then return `None`. Otherwise, the choice
-    /// of trap code depends on the `tabletrap` flag. The default trap
-    /// code is `HeapOutOfBounds`, but with `tabletrap` set, the trap
-    /// code is instead `TableOutOfBounds`.
+    /// Get the trap code to report if this memory access traps.
+    ///
+    /// A `None` trap code indicates that this memory access does not trap.
     pub const fn trap_code(self) -> Option<TrapCode> {
-        if self.notrap() {
-            None
-        } else if self.tabletrap() {
-            Some(TrapCode::TableOutOfBounds)
-        } else {
-            Some(TrapCode::HeapOutOfBounds)
+        // NB: keep this encoding in sync with `with_trap_code` below.
+        //
+        // Also note that the default, all zeros, is `HeapOutOfBounds`. It is
+        // intentionally not `None` so memory operations are all considered
+        // effect-ful by default.
+        match (self.bits & MASK_TRAP_CODE) >> TRAP_CODE_OFFSET {
+            0b0000 => Some(TrapCode::HeapOutOfBounds),
+            0b0001 => Some(TrapCode::StackOverflow),
+            0b0010 => Some(TrapCode::HeapMisaligned),
+            0b0011 => Some(TrapCode::TableOutOfBounds),
+            0b0100 => Some(TrapCode::IndirectCallToNull),
+            0b0101 => Some(TrapCode::BadSignature),
+            0b0110 => Some(TrapCode::IntegerOverflow),
+            0b0111 => Some(TrapCode::IntegerDivisionByZero),
+            0b1000 => Some(TrapCode::BadConversionToInteger),
+            0b1001 => Some(TrapCode::UnreachableCodeReached),
+            0b1010 => Some(TrapCode::Interrupt),
+            0b1011 => Some(TrapCode::NullReference),
+            // 0b1100 => {} not allocated
+            // 0b1101 => {} not allocated
+            // 0b1110 => {} not allocated
+            0b1111 => None,
+            _ => unreachable!(),
         }
     }
+
+    /// Configures these flags with the specified trap code `code`.
+    ///
+    /// Note that `TrapCode::User(_)` cannot be set in `MemFlags`. A trap code
+    /// indicates that this memory operation cannot be optimized away and it
+    /// must "stay where it is" in the programs. Traps are considered side
+    /// effects, for example, and have meaning through the trap code that is
+    /// communicated and which instruction trapped.
+    pub const fn with_trap_code(mut self, code: Option<TrapCode>) -> Self {
+        let bits = match code {
+            Some(TrapCode::HeapOutOfBounds) => 0b0000,
+            Some(TrapCode::StackOverflow) => 0b0001,
+            Some(TrapCode::HeapMisaligned) => 0b0010,
+            Some(TrapCode::TableOutOfBounds) => 0b0011,
+            Some(TrapCode::IndirectCallToNull) => 0b0100,
+            Some(TrapCode::BadSignature) => 0b0101,
+            Some(TrapCode::IntegerOverflow) => 0b0110,
+            Some(TrapCode::IntegerDivisionByZero) => 0b0111,
+            Some(TrapCode::BadConversionToInteger) => 0b1000,
+            Some(TrapCode::UnreachableCodeReached) => 0b1001,
+            Some(TrapCode::Interrupt) => 0b1010,
+            Some(TrapCode::NullReference) => 0b1011,
+            None => 0b1111,
+
+            Some(TrapCode::User(_)) => panic!("cannot set user trap code in mem flags"),
+        };
+        self.bits &= !MASK_TRAP_CODE;
+        self.bits |= bits << TRAP_CODE_OFFSET;
+        self
+    }
 }
 
 impl fmt::Display for MemFlags {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        for (i, n) in NAMES.iter().enumerate() {
-            if self.bits & (1 << i) != 0 {
-                write!(f, " {}", n)?;
-            }
+        match self.trap_code() {
+            None => write!(f, " notrap")?,
+            // This is the default trap code, so don't print anything extra
+            // for this.
+            Some(TrapCode::HeapOutOfBounds) => {}
+            Some(t) => write!(f, " {t}")?,
+        }
+        if self.aligned() {
+            write!(f, " aligned")?;
+        }
+        if self.readonly() {
+            write!(f, " readonly")?;
+        }
+        if self.read_bit(BIT_BIG_ENDIAN) {
+            write!(f, " big")?;
+        }
+        if self.read_bit(BIT_LITTLE_ENDIAN) {
+            write!(f, " little")?;
+        }
+        if self.checked() {
+            write!(f, " checked")?;
+        }
+        match self.alias_region() {
+            None => {}
+            Some(AliasRegion::Heap) => write!(f, " heap")?,
+            Some(AliasRegion::Table) => write!(f, " table")?,
+            Some(AliasRegion::Vmctx) => write!(f, " vmctx")?,
         }
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn roundtrip_traps() {
+        for trap in TrapCode::non_user_traps().iter().copied() {
+            let flags = MemFlags::new().with_trap_code(Some(trap));
+            assert_eq!(flags.trap_code(), Some(trap));
+        }
+        let flags = MemFlags::new().with_trap_code(None);
+        assert_eq!(flags.trap_code(), None);
+    }
+
+    #[test]
+    fn cannot_set_big_and_little() {
+        let mut big = MemFlags::new().with_endianness(Endianness::Big);
+        assert!(big.set_by_name("little").is_err());
+
+        let mut little = MemFlags::new().with_endianness(Endianness::Little);
+        assert!(little.set_by_name("big").is_err());
+    }
+
+    #[test]
+    fn only_one_region() {
+        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Heap));
+        assert!(big.set_by_name("table").is_err());
+        assert!(big.set_by_name("vmctx").is_err());
+
+        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Table));
+        assert!(big.set_by_name("heap").is_err());
+        assert!(big.set_by_name("vmctx").is_err());
+
+        let mut big = MemFlags::new().with_alias_region(Some(AliasRegion::Vmctx));
+        assert!(big.set_by_name("heap").is_err());
+        assert!(big.set_by_name("table").is_err());
+    }
+}
diff --git a/cranelift/codegen/src/ir/mod.rs b/cranelift/codegen/src/ir/mod.rs
index aff5078a7..445496e5e 100644
--- a/cranelift/codegen/src/ir/mod.rs
+++ b/cranelift/codegen/src/ir/mod.rs
@@ -53,7 +53,7 @@ pub use crate::ir::jumptable::JumpTableData;
 pub use crate::ir::known_symbol::KnownSymbol;
 pub use crate::ir::layout::Layout;
 pub use crate::ir::libcall::{get_probestack_funcref, LibCall};
-pub use crate::ir::memflags::{Endianness, MemFlags};
+pub use crate::ir::memflags::{AliasRegion, Endianness, MemFlags};
 pub use crate::ir::memtype::{MemoryTypeData, MemoryTypeField};
 pub use crate::ir::pcc::{BaseExpr, Expr, Fact, FactContext, PccError, PccResult};
 pub use crate::ir::progpoint::ProgramPoint;
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index 440840c4d..31a5c25c3 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -99,17 +99,14 @@ impl ABIMachineSpec for AArch64MachineDeps {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         if matches!(call_conv, isa::CallConv::Tail | isa::CallConv::Winch) {
             return compute_arg_locs_tail(params, add_ret_area_ptr, args);
         }
@@ -1291,14 +1288,11 @@ impl AArch64CallSite {
     }
 }
 
-fn compute_arg_locs_tail<'a, I>(
-    params: I,
+fn compute_arg_locs_tail(
+    params: &[ir::AbiParam],
     add_ret_area_ptr: bool,
-    mut args: ArgsAccumulator<'_>,
-) -> CodegenResult<(u32, Option<usize>)>
-where
-    I: IntoIterator<Item = &'a ir::AbiParam>,
-{
+    mut args: ArgsAccumulator,
+) -> CodegenResult<(u32, Option<usize>)> {
     let mut xregs = ALL_CLOBBERS
         .into_iter()
         .filter(|r| r.class() == RegClass::Int)
diff --git a/cranelift/codegen/src/isa/riscv64/abi.rs b/cranelift/codegen/src/isa/riscv64/abi.rs
index 0f0d32e8f..c119fe312 100644
--- a/cranelift/codegen/src/isa/riscv64/abi.rs
+++ b/cranelift/codegen/src/isa/riscv64/abi.rs
@@ -87,17 +87,14 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         assert_ne!(
             call_conv,
             isa::CallConv::Winch,
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index af34bb5a4..7c48578a8 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -220,17 +220,14 @@ impl ABIMachineSpec for S390xMachineDeps {
         8
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         assert_ne!(
             call_conv,
             isa::CallConv::Tail,
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index eeda5b6cd..9b8eb0764 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -101,17 +101,14 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         let is_fastcall = call_conv.extends_windows_fastcall();
 
         let mut next_gpr = 0;
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 9bb01775e..31cc17518 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -1485,20 +1485,12 @@
 
 ;;;; Rules for `insertlane` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(rule (lower (insertlane vec @ (value_type ty) val (u8_from_uimm8 idx)))
-      (vec_insert_lane ty vec val idx))
-
-;; Helper function used below for `insertlane` but also here for other
-;; lowerings.
-;;
-;; Note that the `Type` used here is the type of vector the insertion is
-;; happening into, or the type of the first `Reg` argument.
-(decl vec_insert_lane (Type Xmm RegMem u8) Xmm)
-
-;; i8x16.replace_lane
-(rule 1 (vec_insert_lane $I8X16 vec val idx)
-        (if-let $true (use_sse41))
-        (x64_pinsrb vec val idx))
+(rule 1 (lower (insertlane vec @ (value_type $I8X16) val (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_pinsrb vec val idx))
+(rule 2 (lower (insertlane vec @ (value_type $I8X16) (sinkable_load_exact val) (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_pinsrb vec val idx))
 
 ;; This lowering is particularly unoptimized and is mostly just here to work
 ;; rather than here to be fast. Requiring SSE 4.1 for the above lowering isn't
@@ -1524,7 +1516,7 @@
 ;; This all, laboriously, gets the `val` into the desired lane so it's then
 ;; `por`'d with the original vec-with-a-hole to produce the final result of the
 ;; insertion.
-(rule (vec_insert_lane $I8X16 vec val n)
+(rule (lower (insertlane vec @ (value_type $I8X16) val (u8_from_uimm8 n)))
       (let ((vec_with_hole Xmm (x64_pand vec (insert_i8x16_lane_hole n)))
             (val Gpr (x64_movzx (ExtMode.BL) val))
             (val Gpr (x64_shl $I32 val (Imm8Reg.Imm8 (u8_shl (u8_and n 3) 3))))
@@ -1540,102 +1532,108 @@
 (rule (insert_i8x16_lane_pshufd_imm 2) 0b01_00_01_01)
 (rule (insert_i8x16_lane_pshufd_imm 3) 0b00_01_01_01)
 
+
 ;; i16x8.replace_lane
-(rule (vec_insert_lane $I16X8 vec val idx)
-      (x64_pinsrw vec val idx))
+(rule (lower (insertlane vec @ (value_type $I16X8) val (u8_from_uimm8 idx)))
+  (x64_pinsrw vec val idx))
+(rule 1 (lower (insertlane vec @ (value_type $I16X8) (sinkable_load_exact val) (u8_from_uimm8 idx)))
+  (x64_pinsrw vec val idx))
 
 ;; i32x4.replace_lane
-(rule 1 (vec_insert_lane $I32X4 vec val idx)
+(rule 1 (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 idx)))
         (if-let $true (use_sse41))
         (x64_pinsrd vec val idx))
 
-(rule (vec_insert_lane $I32X4 vec val 0)
-      (x64_movss_regmove vec (x64_movd_to_xmm val)))
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 0)))
+  (x64_movss_regmove vec (x64_movd_to_xmm val)))
 
 ;; tmp    = [ vec[1] vec[0] val[1] val[0] ]
 ;; result = [ vec[3] vec[2] tmp[0] tmp[2] ]
-(rule (vec_insert_lane $I32X4 vec val 1)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 1)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps (x64_punpcklqdq val vec) vec 0b11_10_00_10)))
 
 ;; tmp    = [ vec[0] vec[3] val[0] val[0] ]
 ;; result = [ tmp[2] tmp[0] vec[1] vec[0] ]
-(rule (vec_insert_lane $I32X4 vec val 2)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 2)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps vec (x64_shufps val vec 0b00_11_00_00) 0b10_00_01_00)))
 
 ;; tmp    = [ vec[3] vec[2] val[1] val[0] ]
 ;; result = [ tmp[0] tmp[2] vec[1] vec[0] ]
-(rule (vec_insert_lane $I32X4 vec val 3)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 3)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps vec (x64_shufps val vec 0b11_10_01_00) 0b00_10_01_00)))
 
 ;; i64x2.replace_lane
-(rule 1 (vec_insert_lane $I64X2 vec val idx)
+(rule 1 (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 idx)))
         (if-let $true (use_sse41))
         (x64_pinsrq vec val idx))
-(rule (vec_insert_lane $I64X2 vec val 0)
+(rule (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 0)))
       (x64_movsd_regmove vec (x64_movq_to_xmm val)))
-(rule (vec_insert_lane $I64X2 vec val 1)
+(rule (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 1)))
       (x64_punpcklqdq vec (x64_movq_to_xmm val)))
 
+(rule 1 (lower (insertlane vec @ (value_type $F32X4) (sinkable_load val) (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_insertps vec val (sse_insertps_lane_imm idx)))
+(rule (lower (insertlane vec @ (value_type $F32X4) val (u8_from_uimm8 idx)))
+  (f32x4_insertlane vec val idx))
+
+;; Helper function used below for `insertlane` but also here for other
+(decl f32x4_insertlane (Xmm Xmm u8) Xmm)
+
 ;; f32x4.replace_lane
-(rule 1 (vec_insert_lane $F32X4 vec val idx)
+(rule 1 (f32x4_insertlane vec val idx)
         (if-let $true (use_sse41))
         (x64_insertps vec val (sse_insertps_lane_imm idx)))
 
-;; f32x4.replace_lane 0 - without insertps
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 0)
+;; External rust code used to calculate the immediate value to `insertps`.
+(decl sse_insertps_lane_imm (u8) u8)
+(extern constructor sse_insertps_lane_imm sse_insertps_lane_imm)
+
+;; f32x4.replace_lane 0
+(rule (f32x4_insertlane vec val 0)
       (x64_movss_regmove vec val))
 
-;; f32x4.replace_lane 1 - without insertps
+;; f32x4.replace_lane 1
 ;; tmp    = [ vec[1] vec[0] val[1] val[0] ]
 ;; result = [ vec[3] vec[2] tmp[0] tmp[2] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 1)
+(rule (f32x4_insertlane vec val 1)
       (let ((tmp Xmm (x64_movlhps val vec)))
         (x64_shufps tmp vec 0b11_10_00_10)))
 
-;; f32x4.replace_lane 2 - without insertps
+;; f32x4.replace_lane 2
 ;; tmp    = [ vec[0] vec[3] val[0] val[0] ]
 ;; result = [ tmp[2] tmp[0] vec[1] vec[0] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 2)
+(rule (f32x4_insertlane vec val 2)
       (let ((tmp Xmm (x64_shufps val vec 0b00_11_00_00)))
         (x64_shufps vec tmp 0b10_00_01_00)))
 
-;; f32x4.replace_lane 3 - without insertps
+;; f32x4.replace_lane 3
 ;; tmp    = [ vec[3] vec[2] val[1] val[0] ]
 ;; result = [ tmp[0] tmp[2] vec[1] vec[0] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 3)
+(rule (f32x4_insertlane vec val 3)
       (let ((tmp Xmm (x64_shufps val vec 0b11_10_01_00)))
         (x64_shufps vec tmp 0b00_10_01_00)))
 
-;; Recursively delegate to the above rules by loading from memory first.
-(rule (vec_insert_lane $F32X4 vec (RegMem.Mem addr) idx)
-      (vec_insert_lane $F32X4 vec (x64_movss_load addr) idx))
-
-;; External rust code used to calculate the immediate value to `insertps`.
-(decl sse_insertps_lane_imm (u8) u8)
-(extern constructor sse_insertps_lane_imm sse_insertps_lane_imm)
-
 ;; f64x2.replace_lane 0
 ;;
 ;; Here the `movsd` instruction is used specifically to specialize moving
 ;; into the fist lane where unlike above cases we're not using the lane
 ;; immediate as an immediate to the instruction itself.
-(rule (vec_insert_lane $F64X2 vec (RegMem.Reg val) 0)
+(rule (lower (insertlane vec @ (value_type $F64X2) val (u8_from_uimm8 0)))
       (x64_movsd_regmove vec val))
-(rule (vec_insert_lane $F64X2 vec (RegMem.Mem val) 0)
-      (x64_movsd_regmove vec (x64_movsd_load val)))
 
 ;; f64x2.replace_lane 1
 ;;
 ;; Here the `movlhps` instruction is used specifically to specialize moving
 ;; into the second lane where unlike above cases we're not using the lane
 ;; immediate as an immediate to the instruction itself.
-(rule (vec_insert_lane $F64X2 vec val 1)
+(rule (lower (insertlane vec @ (value_type $F64X2) val (u8_from_uimm8 1)))
       (x64_movlhps vec val))
 
 ;;;; Rules for `smin`, `smax`, `umin`, `umax` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2804,9 +2802,9 @@
             (x64_pshufd y 3)
             (x64_pshufd z 3)))
 
-          (tmp Xmm (vec_insert_lane $F32X4 x0 x1 1))
-          (tmp Xmm (vec_insert_lane $F32X4 tmp x2 2))
-          (tmp Xmm (vec_insert_lane $F32X4 tmp x3 3))
+          (tmp Xmm (f32x4_insertlane x0 x1 1))
+          (tmp Xmm (f32x4_insertlane tmp x2 2))
+          (tmp Xmm (f32x4_insertlane tmp x3 3))
         )
         tmp))
 (rule (lower (has_type $F64X2 (fma x y z)))
@@ -2820,7 +2818,7 @@
             (x64_pshufd y 0xee)
             (x64_pshufd z 0xee)))
         )
-        (vec_insert_lane $F64X2 x0 x1 1)))
+        (x64_movlhps x0 x1)))
 
 
 ;; Special case for when the `fma` feature is active and a native instruction
@@ -4030,11 +4028,11 @@
           (libcall LibCall (round_libcall $F32 imm))
           (result Xmm (libcall_1 libcall a))
           (a1 Xmm (libcall_1 libcall (x64_pshufd a 1)))
-          (result Xmm (vec_insert_lane $F32X4 result a1 1))
+          (result Xmm (f32x4_insertlane result a1 1))
           (a2 Xmm (libcall_1 libcall (x64_pshufd a 2)))
-          (result Xmm (vec_insert_lane $F32X4 result a2 2))
+          (result Xmm (f32x4_insertlane result a2 2))
           (a3 Xmm (libcall_1 libcall (x64_pshufd a 3)))
-          (result Xmm (vec_insert_lane $F32X4 result a3 3))
+          (result Xmm (f32x4_insertlane result a3 3))
         )
         result))
 (rule (x64_round $F64X2 (RegMem.Reg a) imm)
@@ -4042,9 +4040,8 @@
           (libcall LibCall (round_libcall $F64 imm))
           (result Xmm (libcall_1 libcall a))
           (a1 Xmm (libcall_1 libcall (x64_pshufd a 0b00_00_11_10)))
-          (result Xmm (vec_insert_lane $F64X2 result a1 1))
         )
-        result))
+        (x64_movlhps result a1)))
 (rule (x64_round ty (RegMem.Mem addr) imm)
       (x64_round ty (RegMem.Reg (x64_load ty addr (ExtKind.ZeroExtend))) imm))
 
diff --git a/cranelift/codegen/src/isa/x64/pcc.rs b/cranelift/codegen/src/isa/x64/pcc.rs
index bc826c0d8..64a8acb7d 100644
--- a/cranelift/codegen/src/isa/x64/pcc.rs
+++ b/cranelift/codegen/src/isa/x64/pcc.rs
@@ -4,7 +4,7 @@ use crate::ir::pcc::*;
 use crate::ir::types::*;
 use crate::isa::x64::args::AvxOpcode;
 use crate::isa::x64::inst::args::{
-    AluRmiROpcode, Amode, Gpr, Imm8Reg, RegMem, RegMemImm, ShiftKind, SyntheticAmode,
+    AluRmiROpcode, Amode, Gpr, Imm8Reg, RegMem, RegMemImm, ShiftKind, SseOpcode, SyntheticAmode,
     ToWritableReg, CC,
 };
 use crate::isa::x64::inst::Inst;
@@ -259,8 +259,8 @@ pub(crate) fn check(
                 }
                 RegMem::Reg { .. } => {}
             }
-            undefined_result(ctx, vcode, dst_lo, 64, 64)?;
-            undefined_result(ctx, vcode, dst_hi, 64, 64)?;
+            undefined_result(ctx, vcode, dst_lo, 64, size.to_bits().into())?;
+            undefined_result(ctx, vcode, dst_hi, 64, size.to_bits().into())?;
             Ok(())
         }
         Inst::Mul8 { dst, ref src2, .. } => {
@@ -270,7 +270,7 @@ pub(crate) fn check(
                 }
                 RegMem::Reg { .. } => {}
             }
-            undefined_result(ctx, vcode, dst, 64, 64)?;
+            undefined_result(ctx, vcode, dst, 64, 16)?;
             Ok(())
         }
         Inst::IMul {
@@ -285,7 +285,7 @@ pub(crate) fn check(
                 }
                 RegMem::Reg { .. } => {}
             }
-            undefined_result(ctx, vcode, dst, 64, 64)?;
+            undefined_result(ctx, vcode, dst, 64, size.to_bits().into())?;
             Ok(())
         }
         Inst::IMulImm {
@@ -300,7 +300,7 @@ pub(crate) fn check(
                 }
                 RegMem::Reg { .. } => {}
             }
-            undefined_result(ctx, vcode, dst, 64, 64)?;
+            undefined_result(ctx, vcode, dst, 64, size.to_bits().into())?;
             Ok(())
         }
         Inst::CheckedSRemSeq {
@@ -545,6 +545,35 @@ pub(crate) fn check(
             ensure_no_fact(vcode, dst.to_writable_reg().to_reg())
         }
 
+        Inst::XmmUnaryRmRUnaligned {
+            dst,
+            ref src,
+            op: SseOpcode::Movss,
+            ..
+        } => {
+            match <&RegMem>::from(src) {
+                RegMem::Mem { ref addr } => {
+                    check_load(ctx, None, addr, vcode, F32, 32)?;
+                }
+                RegMem::Reg { .. } => {}
+            }
+            ensure_no_fact(vcode, dst.to_writable_reg().to_reg())
+        }
+        Inst::XmmUnaryRmRUnaligned {
+            dst,
+            ref src,
+            op: SseOpcode::Movsd,
+            ..
+        } => {
+            match <&RegMem>::from(src) {
+                RegMem::Mem { ref addr } => {
+                    check_load(ctx, None, addr, vcode, F64, 64)?;
+                }
+                RegMem::Reg { .. } => {}
+            }
+            ensure_no_fact(vcode, dst.to_writable_reg().to_reg())
+        }
+
         // NOTE: it's assumed that all of these cases perform 128-bit loads, but this hasn't been
         // verified. The effect of this will be spurious PCC failures when these instructions are
         // involved.
@@ -597,17 +626,17 @@ pub(crate) fn check(
             src: ref src2,
             ..
         } => {
-            let size = match op {
-                AvxOpcode::Vmovss => 32,
-                AvxOpcode::Vmovsd => 64,
+            let (ty, size) = match op {
+                AvxOpcode::Vmovss => (F32, 32),
+                AvxOpcode::Vmovsd => (F64, 64),
 
                 // We assume all other operations happen on 128-bit values.
-                _ => 128,
+                _ => (I8X16, 128),
             };
 
             match <&RegMem>::from(src2) {
                 RegMem::Mem { ref addr } => {
-                    check_load(ctx, None, addr, vcode, I8X16, size)?;
+                    check_load(ctx, None, addr, vcode, ty, size)?;
                 }
                 RegMem::Reg { .. } => {}
             }
@@ -652,6 +681,24 @@ pub(crate) fn check(
 
         Inst::XmmToGprVex { dst, .. } => undefined_result(ctx, vcode, dst, 64, 64),
 
+        Inst::XmmMovRM {
+            ref dst,
+            op: SseOpcode::Movss,
+            ..
+        } => {
+            check_store(ctx, None, dst, vcode, F32)?;
+            Ok(())
+        }
+
+        Inst::XmmMovRM {
+            ref dst,
+            op: SseOpcode::Movsd,
+            ..
+        } => {
+            check_store(ctx, None, dst, vcode, F64)?;
+            Ok(())
+        }
+
         Inst::XmmMovRM { ref dst, .. } | Inst::XmmMovRMImm { ref dst, .. } => {
             check_store(ctx, None, dst, vcode, I8X16)?;
             Ok(())
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 4700bbaa2..4af7bc343 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -408,16 +408,14 @@ pub trait ABIMachineSpec {
     /// Returns the stack-space used (rounded up to as alignment requires), and
     /// if `add_ret_area_ptr` was passed, the index of the extra synthetic arg
     /// that was added.
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>;
+        args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)>;
 
     /// Returns the offset from FP to the argument area, i.e., jumping over the saved FP, return
     /// address, and maybe other standard elements depending on ABI (e.g. Wasm TLS reg).
@@ -827,8 +825,13 @@ impl SigSet {
         sig: &ir::Signature,
         flags: &settings::Flags,
     ) -> CodegenResult<SigData> {
-        let sret = missing_struct_return(sig);
-        let returns = sret.as_ref().into_iter().chain(&sig.returns);
+        use std::borrow::Cow;
+
+        let returns = if let Some(sret) = missing_struct_return(sig) {
+            Cow::from_iter(std::iter::once(&sret).chain(&sig.returns).copied())
+        } else {
+            Cow::from(sig.returns.as_slice())
+        };
 
         // Compute args and retvals from signature. Handle retvals first,
         // because we may need to add a return-area arg to the args.
@@ -839,7 +842,7 @@ impl SigSet {
         let (sized_stack_ret_space, _) = M::compute_arg_locs(
             sig.call_conv,
             flags,
-            returns,
+            &returns,
             ArgsOrRets::Rets,
             /* extra ret-area ptr = */ false,
             ArgsAccumulator::new(&mut self.abi_args),
@@ -2328,42 +2331,12 @@ impl<M: ABIMachineSpec> CallSite<M> {
         idx: usize,
         from_regs: ValueRegs<Reg>,
     ) -> SmallVec<[(VReg, ArgLoc); 2]> {
-        let mut insts = SmallInstVec::new();
         let mut locs = smallvec![];
         let word_rc = M::word_reg_class();
         let word_bits = M::word_bits() as usize;
 
-        // How many temps do we need for extends? Allocate them ahead
-        // of time, since we can't do it while we're iterating over
-        // the sig and immutably borrowing `ctx`.
-        let needed_tmps = match &ctx.sigs().args(self.sig)[idx] {
-            &ABIArg::Slots { ref slots, .. } => slots
-                .iter()
-                .map(|slot| match slot {
-                    &ABIArgSlot::Reg { extension, .. }
-                        if extension != ir::ArgumentExtension::None =>
-                    {
-                        1
-                    }
-                    &ABIArgSlot::Reg { ty, .. } if ty.is_ref() => 1,
-                    &ABIArgSlot::Reg { .. } => 0,
-                    &ABIArgSlot::Stack { extension, .. }
-                        if extension != ir::ArgumentExtension::None =>
-                    {
-                        1
-                    }
-                    &ABIArgSlot::Stack { .. } => 0,
-                })
-                .sum(),
-            ABIArg::ImplicitPtrArg { .. } => 1,
-            _ => 0,
-        };
-        let mut temps: SmallVec<[Writable<Reg>; 16]> = (0..needed_tmps)
-            .map(|_| ctx.alloc_tmp(M::word_type()).only_reg().unwrap())
-            .collect();
-
-        match &ctx.sigs().args(self.sig)[idx] {
-            &ABIArg::Slots { ref slots, .. } => {
+        match ctx.sigs().args(self.sig)[idx].clone() {
+            ABIArg::Slots { ref slots, .. } => {
                 assert_eq!(from_regs.len(), slots.len());
                 for (slot, from_reg) in slots.iter().zip(from_regs.regs().iter()) {
                     match slot {
@@ -2379,8 +2352,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                     _ => unreachable!(),
                                 };
                                 let extend_result =
-                                    temps.pop().expect("Must have allocated enough temps");
-                                insts.push(M::gen_extend(
+                                    ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                ctx.emit(M::gen_extend(
                                     extend_result,
                                     *from_reg,
                                     signed,
@@ -2393,9 +2366,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                 // passed as a copy; the original vreg
                                 // is constrained to the stack and
                                 // this copy is in a reg.
-                                let ref_copy =
-                                    temps.pop().expect("Must have allocated enough temps");
-                                insts.push(M::gen_move(ref_copy, *from_reg, M::word_type()));
+                                let ref_copy = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                ctx.emit(M::gen_move(ref_copy, *from_reg, M::word_type()));
 
                                 locs.push((ref_copy.to_reg().into(), ArgLoc::Reg(reg.into())));
                             } else {
@@ -2418,8 +2390,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                         _ => unreachable!(),
                                     };
                                     let extend_result =
-                                        temps.pop().expect("Must have allocated enough temps");
-                                    insts.push(M::gen_extend(
+                                        ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                    ctx.emit(M::gen_extend(
                                         extend_result,
                                         *from_reg,
                                         signed,
@@ -2439,10 +2411,10 @@ impl<M: ABIMachineSpec> CallSite<M> {
                     }
                 }
             }
-            &ABIArg::StructArg { pointer, .. } => {
+            ABIArg::StructArg { pointer, .. } => {
                 assert!(pointer.is_none()); // Only supported via ISLE.
             }
-            &ABIArg::ImplicitPtrArg {
+            ABIArg::ImplicitPtrArg {
                 offset,
                 pointer,
                 ty,
@@ -2451,10 +2423,10 @@ impl<M: ABIMachineSpec> CallSite<M> {
                 assert_eq!(from_regs.len(), 1);
                 let vreg = from_regs.regs()[0];
                 let amode = StackAMode::SPOffset(offset, ty);
-                let tmp = temps[0];
-                insts.push(M::gen_get_stack_addr(amode, tmp, ty));
+                let tmp = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                ctx.emit(M::gen_get_stack_addr(amode, tmp, ty));
                 let tmp = tmp.to_reg();
-                insts.push(M::gen_store_base_offset(tmp, 0, vreg, ty));
+                ctx.emit(M::gen_store_base_offset(tmp, 0, vreg, ty));
                 let loc = match pointer {
                     ABIArgSlot::Reg { reg, .. } => ArgLoc::Reg(reg.into()),
                     ABIArgSlot::Stack { offset, .. } => {
@@ -2466,10 +2438,6 @@ impl<M: ABIMachineSpec> CallSite<M> {
             }
         }
 
-        for inst in insts {
-            ctx.emit(inst);
-        }
-
         locs
     }
 
diff --git a/cranelift/codegen/src/nan_canonicalization.rs b/cranelift/codegen/src/nan_canonicalization.rs
index bb791d60b..c1bc16f77 100644
--- a/cranelift/codegen/src/nan_canonicalization.rs
+++ b/cranelift/codegen/src/nan_canonicalization.rs
@@ -38,6 +38,10 @@ fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
                 || opcode == Opcode::Nearest
                 || opcode == Opcode::Sqrt
                 || opcode == Opcode::Trunc
+                || opcode == Opcode::Fdemote
+                || opcode == Opcode::Fpromote
+                || opcode == Opcode::FvpromoteLow
+                || opcode == Opcode::Fvdemote
         }
         InstructionData::Binary { opcode, .. } => {
             opcode == Opcode::Fadd
@@ -48,24 +52,6 @@ fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
                 || opcode == Opcode::Fsub
         }
         InstructionData::Ternary { opcode, .. } => opcode == Opcode::Fma,
-
-        // bitcasts coming from arbitrary integers need canonicalization as the
-        // integer isn't known to necessarily be a canonical NaN
-        InstructionData::LoadNoOffset {
-            opcode: Opcode::Bitcast,
-            arg,
-            flags: _,
-        } => {
-            let ret = pos.func.dfg.first_result(inst);
-            let ret_type = pos.func.dfg.value_type(ret);
-            let arg_type = pos.func.dfg.value_type(arg);
-            match (arg_type, ret_type) {
-                (types::I32, types::F32) => true,
-                (types::I64, types::F64) => true,
-                _ => false,
-            }
-        }
-
         _ => false,
     }
 }
diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs
index 6dc17eac1..4e2506bdb 100644
--- a/cranelift/fuzzgen/src/function_generator.rs
+++ b/cranelift/fuzzgen/src/function_generator.rs
@@ -9,8 +9,8 @@ use cranelift::codegen::ir::instructions::{InstructionFormat, ResolvedConstraint
 use cranelift::codegen::ir::stackslot::StackSize;
 
 use cranelift::codegen::ir::{
-    types::*, AtomicRmwOp, Block, ConstantData, Endianness, ExternalName, FuncRef, Function,
-    LibCall, Opcode, SigRef, Signature, StackSlot, UserExternalName, UserFuncName, Value,
+    types::*, AliasRegion, AtomicRmwOp, Block, ConstantData, Endianness, ExternalName, FuncRef,
+    Function, LibCall, Opcode, SigRef, Signature, StackSlot, UserExternalName, UserFuncName, Value,
 };
 use cranelift::codegen::isa::CallConv;
 use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
@@ -1176,12 +1176,12 @@ impl AACategory {
     }
 
     pub fn update_memflags(&self, flags: &mut MemFlags) {
-        match self {
-            AACategory::Other => {}
-            AACategory::Heap => flags.set_heap(),
-            AACategory::Table => flags.set_table(),
-            AACategory::VmCtx => flags.set_vmctx(),
-        }
+        flags.set_alias_region(match self {
+            AACategory::Other => None,
+            AACategory::Heap => Some(AliasRegion::Heap),
+            AACategory::Table => Some(AliasRegion::Table),
+            AACategory::VmCtx => Some(AliasRegion::Vmctx),
+        })
     }
 }
 
diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs
index 03c29fa4a..0dc9289ef 100644
--- a/cranelift/reader/src/parser.rs
+++ b/cranelift/reader/src/parser.rs
@@ -887,16 +887,18 @@ impl<'a> Parser<'a> {
     }
 
     // Match and a consume a possibly empty sequence of memory operation flags.
-    fn optional_memflags(&mut self) -> MemFlags {
+    fn optional_memflags(&mut self) -> ParseResult<MemFlags> {
         let mut flags = MemFlags::new();
         while let Some(Token::Identifier(text)) = self.token() {
-            if flags.set_by_name(text) {
-                self.consume();
-            } else {
-                break;
+            match flags.set_by_name(text) {
+                Ok(true) => {
+                    self.consume();
+                }
+                Ok(false) => break,
+                Err(msg) => return err!(self.loc, msg),
             }
         }
-        flags
+        Ok(flags)
     }
 
     // Match and consume an identifier.
@@ -1570,7 +1572,7 @@ impl<'a> Parser<'a> {
                     "expected '.' followed by type in load global value decl",
                 )?;
                 let global_type = self.match_type("expected load type")?;
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let base = self.match_gv("expected global value: gv«n»")?;
                 let offset = self.optional_offset32()?;
 
@@ -3036,7 +3038,7 @@ impl<'a> Parser<'a> {
                 }
             }
             InstructionFormat::Load => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let addr = self.match_value("expected SSA value address")?;
                 let offset = self.optional_offset32()?;
                 InstructionData::Load {
@@ -3047,7 +3049,7 @@ impl<'a> Parser<'a> {
                 }
             }
             InstructionFormat::Store => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let arg = self.match_value("expected SSA value operand")?;
                 self.match_token(Token::Comma, "expected ',' between operands")?;
                 let addr = self.match_value("expected SSA value address")?;
@@ -3070,7 +3072,7 @@ impl<'a> Parser<'a> {
                 InstructionData::CondTrap { opcode, arg, code }
             }
             InstructionFormat::AtomicCas => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let addr = self.match_value("expected SSA value address")?;
                 self.match_token(Token::Comma, "expected ',' between operands")?;
                 let expected = self.match_value("expected SSA value address")?;
@@ -3083,7 +3085,7 @@ impl<'a> Parser<'a> {
                 }
             }
             InstructionFormat::AtomicRmw => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let op = self.match_enum("expected AtomicRmwOp")?;
                 let addr = self.match_value("expected SSA value address")?;
                 self.match_token(Token::Comma, "expected ',' between operands")?;
@@ -3096,7 +3098,7 @@ impl<'a> Parser<'a> {
                 }
             }
             InstructionFormat::LoadNoOffset => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let addr = self.match_value("expected SSA value address")?;
                 InstructionData::LoadNoOffset {
                     opcode,
@@ -3105,7 +3107,7 @@ impl<'a> Parser<'a> {
                 }
             }
             InstructionFormat::StoreNoOffset => {
-                let flags = self.optional_memflags();
+                let flags = self.optional_memflags()?;
                 let arg = self.match_value("expected SSA value operand")?;
                 self.match_token(Token::Comma, "expected ',' between operands")?;
                 let addr = self.match_value("expected SSA value address")?;
diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs
index f4a284d5e..adbe7ea65 100644
--- a/cranelift/wasm/src/code_translator.rs
+++ b/cranelift/wasm/src/code_translator.rs
@@ -177,7 +177,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
                     let addr = builder.ins().global_value(environ.pointer_type(), gv);
                     let mut flags = ir::MemFlags::trusted();
                     // Put globals in the "table" abstract heap category as well.
-                    flags.set_table();
+                    flags.set_alias_region(Some(ir::AliasRegion::Table));
                     builder.ins().load(ty, flags, addr, offset)
                 }
                 GlobalVariable::Custom => environ.translate_custom_global_get(
@@ -194,7 +194,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
                     let addr = builder.ins().global_value(environ.pointer_type(), gv);
                     let mut flags = ir::MemFlags::trusted();
                     // Put globals in the "table" abstract heap category as well.
-                    flags.set_table();
+                    flags.set_alias_region(Some(ir::AliasRegion::Table));
                     let mut val = state.pop1();
                     // Ensure SIMD values are cast to their default Cranelift type, I8x16.
                     if ty.is_vector() {
@@ -659,6 +659,13 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
                 callee,
                 state.peekn(num_args),
             )?;
+            let call = match call {
+                Some(call) => call,
+                None => {
+                    state.reachable = false;
+                    return Ok(());
+                }
+            };
             let inst_results = builder.inst_results(call);
             debug_assert_eq!(
                 inst_results.len(),
@@ -2837,7 +2844,7 @@ where
     // state. This may allow alias analysis to merge redundant loads,
     // etc. when heap accesses occur interleaved with other (table,
     // vmctx, stack) accesses.
-    flags.set_heap();
+    flags.set_alias_region(Some(ir::AliasRegion::Heap));
 
     Ok(Reachability::Reachable((flags, index, addr)))
 }
diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs
index a42a40384..eeecefc71 100644
--- a/cranelift/wasm/src/environ/dummy.rs
+++ b/cranelift/wasm/src/environ/dummy.rs
@@ -349,7 +349,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
+    ) -> WasmResult<Option<ir::Inst>> {
         // Pass the current function's vmctx parameter on to the callee.
         let vmctx = builder
             .func
@@ -376,10 +376,12 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         args.extend(call_args.iter().cloned(), &mut builder.func.dfg.value_lists);
         args.push(vmctx, &mut builder.func.dfg.value_lists);
 
-        Ok(builder
-            .ins()
-            .CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
-            .0)
+        Ok(Some(
+            builder
+                .ins()
+                .CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
+                .0,
+        ))
     }
 
     fn translate_return_call_indirect(
@@ -528,8 +530,8 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         let pointer_type = self.pointer_type();
         self.ensure_table_exists(builder.func, table_index);
         let table = self.tables[table_index].as_ref().unwrap();
-        let table_entry_addr = table.prepare_table_addr(builder, index, pointer_type, true);
-        let flags = ir::MemFlags::trusted().with_table();
+        let (table_entry_addr, flags) =
+            table.prepare_table_addr(builder, index, pointer_type, true);
         let value = builder
             .ins()
             .load(self.reference_type(), flags, table_entry_addr, 0);
@@ -546,8 +548,8 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         let pointer_type = self.pointer_type();
         self.ensure_table_exists(builder.func, table_index);
         let table = self.tables[table_index].as_ref().unwrap();
-        let table_entry_addr = table.prepare_table_addr(builder, index, pointer_type, true);
-        let flags = ir::MemFlags::trusted().with_table();
+        let (table_entry_addr, flags) =
+            table.prepare_table_addr(builder, index, pointer_type, true);
         builder.ins().store(flags, value, table_entry_addr, 0);
         Ok(())
     }
diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs
index 9d13f4e5a..d99696270 100644
--- a/cranelift/wasm/src/environ/spec.rs
+++ b/cranelift/wasm/src/environ/spec.rs
@@ -193,6 +193,8 @@ pub trait FuncEnvironment: TargetEnvironment {
     /// The signature `sig_ref` was previously created by `make_indirect_sig()`.
     ///
     /// Return the call instruction whose results are the WebAssembly return values.
+    /// Returns `None` if this statically traps instead of creating a call
+    /// instruction.
     fn translate_call_indirect(
         &mut self,
         builder: &mut FunctionBuilder,
@@ -201,7 +203,7 @@ pub trait FuncEnvironment: TargetEnvironment {
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst>;
+    ) -> WasmResult<Option<ir::Inst>>;
 
     /// Translate a `return_call` WebAssembly instruction at the builder's
     /// current position.
diff --git a/cranelift/wasm/src/table.rs b/cranelift/wasm/src/table.rs
index ed00221a9..bb5466670 100644
--- a/cranelift/wasm/src/table.rs
+++ b/cranelift/wasm/src/table.rs
@@ -50,7 +50,7 @@ impl TableData {
         mut index: ir::Value,
         addr_ty: ir::Type,
         enable_table_access_spectre_mitigation: bool,
-    ) -> ir::Value {
+    ) -> (ir::Value, ir::MemFlags) {
         let index_ty = pos.func.dfg.value_type(index);
 
         // Start with the bounds check. Trap if `index + 1 > bound`.
@@ -60,16 +60,10 @@ impl TableData {
         let oob = pos
             .ins()
             .icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
-        pos.ins().trapnz(oob, ir::TrapCode::TableOutOfBounds);
 
-        // If Spectre mitigations are enabled, we will use a comparison to
-        // short-circuit the computed table element address to the start
-        // of the table on the misspeculation path when out-of-bounds.
-        let spectre_oob_cmp = if enable_table_access_spectre_mitigation {
-            Some((index, bound))
-        } else {
-            None
-        };
+        if !enable_table_access_spectre_mitigation {
+            pos.ins().trapnz(oob, ir::TrapCode::TableOutOfBounds);
+        }
 
         // Convert `index` to `addr_ty`.
         if index_ty != addr_ty {
@@ -91,14 +85,20 @@ impl TableData {
 
         let element_addr = pos.ins().iadd(base, offset);
 
-        if let Some((index, bound)) = spectre_oob_cmp {
-            let cond = pos
-                .ins()
-                .icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
-            // If out-of-bounds, choose the table base on the misspeculation path.
-            pos.ins().select_spectre_guard(cond, base, element_addr)
+        let base_flags = ir::MemFlags::new()
+            .with_aligned()
+            .with_alias_region(Some(ir::AliasRegion::Table));
+        if enable_table_access_spectre_mitigation {
+            // Short-circuit the computed table element address to a null pointer
+            // when out-of-bounds. The consumer of this address will trap when
+            // trying to access it.
+            let zero = pos.ins().iconst(addr_ty, 0);
+            (
+                pos.ins().select_spectre_guard(oob, zero, element_addr),
+                base_flags.with_trap_code(Some(ir::TrapCode::TableOutOfBounds)),
+            )
         } else {
-            element_addr
+            (element_addr, base_flags.with_trap_code(None))
         }
     }
 }
diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt
index ff92a091e..2075dd2bd 100644
--- a/crates/c-api/CMakeLists.txt
+++ b/crates/c-api/CMakeLists.txt
@@ -107,16 +107,14 @@ else()
 	endif()
 endif()
 
-target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
-	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include)
+target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
 
 include(GNUInstallDirs)
 install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/wasi.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/doc-wasm.h
-	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
-install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.h
-	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.hh
+	${CMAKE_CURRENT_SOURCE_DIR}/include/wasm.h
+	${CMAKE_CURRENT_SOURCE_DIR}/include/wasm.hh
 	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime
 	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml
index 2e6942f45..c54b64d89 100644
--- a/crates/c-api/Cargo.toml
+++ b/crates/c-api/Cargo.toml
@@ -8,7 +8,7 @@ repository = "https://github.com/bytecodealliance/wasmtime"
 readme = "README.md"
 edition.workspace = true
 links = "wasmtime-c-api"
-include = ["include", "src", "wasm-c-api/include", "build.rs"]
+include = ["include", "src", "build.rs"]
 
 [lints]
 workspace = true
diff --git a/crates/c-api/build.rs b/crates/c-api/build.rs
index 7f3f03bd9..7c9151fbe 100644
--- a/crates/c-api/build.rs
+++ b/crates/c-api/build.rs
@@ -1,5 +1,4 @@
 fn main() {
     let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
     println!("cargo:include={dir}/include");
-    println!("cargo:wasm_include={dir}/wasm-c-api/include");
 }
diff --git a/crates/c-api/doxygen.conf b/crates/c-api/doxygen.conf
index 4e43ca407..6c8147e11 100644
--- a/crates/c-api/doxygen.conf
+++ b/crates/c-api/doxygen.conf
@@ -864,7 +864,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = include wasm-c-api/include
+INPUT                  = include
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -2162,8 +2162,7 @@ SEARCH_INCLUDES        = YES
 # preprocessor.
 # This tag requires that the tag SEARCH_INCLUDES is set to YES.
 
-INCLUDE_PATH           = wasm-c-api/include \
-                         include
+INCLUDE_PATH           = include
 
 # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
 # patterns (like *.h and *.hpp) to filter out the header-files in the
diff --git a/crates/c-api/include/wasm.h b/crates/c-api/include/wasm.h
new file mode 100644
index 000000000..c8b3cb7d3
--- /dev/null
+++ b/crates/c-api/include/wasm.h
@@ -0,0 +1,729 @@
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <https://github.com/WebAssembly/wasm-c-api/blob/2ce1367c9d1271c83fb63bef26d896a2f290cd23/include/wasm.h>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+// WebAssembly C API
+
+#ifndef WASM_H
+#define WASM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+#ifndef WASM_API_EXTERN
+#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC)
+#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Auxiliaries
+
+// Machine types
+
+inline void assertions(void) {
+  static_assert(sizeof(float) == sizeof(uint32_t), "incompatible float type");
+  static_assert(sizeof(double) == sizeof(uint64_t), "incompatible double type");
+  static_assert(sizeof(intptr_t) == sizeof(uint32_t) ||
+                sizeof(intptr_t) == sizeof(uint64_t),
+                "incompatible pointer type");
+}
+
+typedef char byte_t;
+typedef float float32_t;
+typedef double float64_t;
+
+
+// Ownership
+
+#define own
+
+// The qualifier `own` is used to indicate ownership of data in this API.
+// It is intended to be interpreted similar to a `const` qualifier:
+//
+// - `own wasm_xxx_t*` owns the pointed-to data
+// - `own wasm_xxx_t` distributes to all fields of a struct or union `xxx`
+// - `own wasm_xxx_vec_t` owns the vector as well as its elements(!)
+// - an `own` function parameter passes ownership from caller to callee
+// - an `own` function result passes ownership from callee to caller
+// - an exception are `own` pointer parameters named `out`, which are copy-back
+//   output parameters passing back ownership from callee to caller
+//
+// Own data is created by `wasm_xxx_new` functions and some others.
+// It must be released with the corresponding `wasm_xxx_delete` function.
+//
+// Deleting a reference does not necessarily delete the underlying object,
+// it merely indicates that this owner no longer uses it.
+//
+// For vectors, `const wasm_xxx_vec_t` is used informally to indicate that
+// neither the vector nor its elements should be modified.
+// TODO: introduce proper `wasm_xxx_const_vec_t`?
+
+
+#define WASM_DECLARE_OWN(name) \
+  typedef struct wasm_##name##_t wasm_##name##_t; \
+  \
+  WASM_API_EXTERN void wasm_##name##_delete(own wasm_##name##_t*);
+
+
+// Vectors
+
+#define WASM_DECLARE_VEC(name, ptr_or_none) \
+  typedef struct wasm_##name##_vec_t { \
+    size_t size; \
+    wasm_##name##_t ptr_or_none* data; \
+  } wasm_##name##_vec_t; \
+  \
+  WASM_API_EXTERN void wasm_##name##_vec_new_empty(own wasm_##name##_vec_t* out); \
+  WASM_API_EXTERN void wasm_##name##_vec_new_uninitialized( \
+    own wasm_##name##_vec_t* out, size_t); \
+  WASM_API_EXTERN void wasm_##name##_vec_new( \
+    own wasm_##name##_vec_t* out, \
+    size_t, own wasm_##name##_t ptr_or_none const[]); \
+  WASM_API_EXTERN void wasm_##name##_vec_copy( \
+    own wasm_##name##_vec_t* out, const wasm_##name##_vec_t*); \
+  WASM_API_EXTERN void wasm_##name##_vec_delete(own wasm_##name##_vec_t*);
+
+
+// Byte vectors
+
+typedef byte_t wasm_byte_t;
+WASM_DECLARE_VEC(byte, )
+
+typedef wasm_byte_vec_t wasm_name_t;
+
+#define wasm_name wasm_byte_vec
+#define wasm_name_new wasm_byte_vec_new
+#define wasm_name_new_empty wasm_byte_vec_new_empty
+#define wasm_name_new_new_uninitialized wasm_byte_vec_new_uninitialized
+#define wasm_name_copy wasm_byte_vec_copy
+#define wasm_name_delete wasm_byte_vec_delete
+
+static inline void wasm_name_new_from_string(
+  own wasm_name_t* out, const char* s
+) {
+  wasm_name_new(out, strlen(s), s);
+}
+
+static inline void wasm_name_new_from_string_nt(
+  own wasm_name_t* out, const char* s
+) {
+  wasm_name_new(out, strlen(s) + 1, s);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Environment
+
+// Configuration
+
+WASM_DECLARE_OWN(config)
+
+WASM_API_EXTERN own wasm_config_t* wasm_config_new(void);
+
+// Embedders may provide custom functions for manipulating configs.
+
+
+// Engine
+
+WASM_DECLARE_OWN(engine)
+
+WASM_API_EXTERN own wasm_engine_t* wasm_engine_new(void);
+WASM_API_EXTERN own wasm_engine_t* wasm_engine_new_with_config(own wasm_config_t*);
+
+
+// Store
+
+WASM_DECLARE_OWN(store)
+
+WASM_API_EXTERN own wasm_store_t* wasm_store_new(wasm_engine_t*);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Type Representations
+
+// Type attributes
+
+typedef uint8_t wasm_mutability_t;
+enum wasm_mutability_enum {
+  WASM_CONST,
+  WASM_VAR,
+};
+
+typedef struct wasm_limits_t {
+  uint32_t min;
+  uint32_t max;
+} wasm_limits_t;
+
+static const uint32_t wasm_limits_max_default = 0xffffffff;
+
+
+// Generic
+
+#define WASM_DECLARE_TYPE(name) \
+  WASM_DECLARE_OWN(name) \
+  WASM_DECLARE_VEC(name, *) \
+  \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t*);
+
+
+// Value Types
+
+WASM_DECLARE_TYPE(valtype)
+
+typedef uint8_t wasm_valkind_t;
+enum wasm_valkind_enum {
+  WASM_I32,
+  WASM_I64,
+  WASM_F32,
+  WASM_F64,
+  WASM_EXTERNREF = 128,
+  WASM_FUNCREF,
+};
+
+WASM_API_EXTERN own wasm_valtype_t* wasm_valtype_new(wasm_valkind_t);
+
+WASM_API_EXTERN wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t*);
+
+static inline bool wasm_valkind_is_num(wasm_valkind_t k) {
+  return k < WASM_EXTERNREF;
+}
+static inline bool wasm_valkind_is_ref(wasm_valkind_t k) {
+  return k >= WASM_EXTERNREF;
+}
+
+static inline bool wasm_valtype_is_num(const wasm_valtype_t* t) {
+  return wasm_valkind_is_num(wasm_valtype_kind(t));
+}
+static inline bool wasm_valtype_is_ref(const wasm_valtype_t* t) {
+  return wasm_valkind_is_ref(wasm_valtype_kind(t));
+}
+
+
+// Function Types
+
+WASM_DECLARE_TYPE(functype)
+
+WASM_API_EXTERN own wasm_functype_t* wasm_functype_new(
+  own wasm_valtype_vec_t* params, own wasm_valtype_vec_t* results);
+
+WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t*);
+WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t*);
+
+
+// Global Types
+
+WASM_DECLARE_TYPE(globaltype)
+
+WASM_API_EXTERN own wasm_globaltype_t* wasm_globaltype_new(
+  own wasm_valtype_t*, wasm_mutability_t);
+
+WASM_API_EXTERN const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t*);
+WASM_API_EXTERN wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t*);
+
+
+// Table Types
+
+WASM_DECLARE_TYPE(tabletype)
+
+WASM_API_EXTERN own wasm_tabletype_t* wasm_tabletype_new(
+  own wasm_valtype_t*, const wasm_limits_t*);
+
+WASM_API_EXTERN const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t*);
+WASM_API_EXTERN const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t*);
+
+
+// Memory Types
+
+WASM_DECLARE_TYPE(memorytype)
+
+WASM_API_EXTERN own wasm_memorytype_t* wasm_memorytype_new(const wasm_limits_t*);
+
+WASM_API_EXTERN const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t*);
+
+
+// Extern Types
+
+WASM_DECLARE_TYPE(externtype)
+
+typedef uint8_t wasm_externkind_t;
+enum wasm_externkind_enum {
+  WASM_EXTERN_FUNC,
+  WASM_EXTERN_GLOBAL,
+  WASM_EXTERN_TABLE,
+  WASM_EXTERN_MEMORY,
+};
+
+WASM_API_EXTERN wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t*);
+
+WASM_API_EXTERN wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t*);
+
+WASM_API_EXTERN wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_externtype_t* wasm_functype_as_externtype_const(const wasm_functype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_globaltype_as_externtype_const(const wasm_globaltype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_tabletype_as_externtype_const(const wasm_tabletype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_memorytype_as_externtype_const(const wasm_memorytype_t*);
+
+WASM_API_EXTERN const wasm_functype_t* wasm_externtype_as_functype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_globaltype_t* wasm_externtype_as_globaltype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_tabletype_t* wasm_externtype_as_tabletype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_memorytype_t* wasm_externtype_as_memorytype_const(const wasm_externtype_t*);
+
+
+// Import Types
+
+WASM_DECLARE_TYPE(importtype)
+
+WASM_API_EXTERN own wasm_importtype_t* wasm_importtype_new(
+  own wasm_name_t* module, own wasm_name_t* name, own wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_name_t* wasm_importtype_module(const wasm_importtype_t*);
+WASM_API_EXTERN const wasm_name_t* wasm_importtype_name(const wasm_importtype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t*);
+
+
+// Export Types
+
+WASM_DECLARE_TYPE(exporttype)
+
+WASM_API_EXTERN own wasm_exporttype_t* wasm_exporttype_new(
+  own wasm_name_t*, own wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t*);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Objects
+
+// Values
+
+struct wasm_ref_t;
+
+typedef struct wasm_val_t {
+  wasm_valkind_t kind;
+  union {
+    int32_t i32;
+    int64_t i64;
+    float32_t f32;
+    float64_t f64;
+    struct wasm_ref_t* ref;
+  } of;
+} wasm_val_t;
+
+WASM_API_EXTERN void wasm_val_delete(own wasm_val_t* v);
+WASM_API_EXTERN void wasm_val_copy(own wasm_val_t* out, const wasm_val_t*);
+
+WASM_DECLARE_VEC(val, )
+
+
+// References
+
+#define WASM_DECLARE_REF_BASE(name) \
+  WASM_DECLARE_OWN(name) \
+  \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t*); \
+  WASM_API_EXTERN bool wasm_##name##_same(const wasm_##name##_t*, const wasm_##name##_t*); \
+  \
+  WASM_API_EXTERN void* wasm_##name##_get_host_info(const wasm_##name##_t*); \
+  WASM_API_EXTERN void wasm_##name##_set_host_info(wasm_##name##_t*, void*); \
+  WASM_API_EXTERN void wasm_##name##_set_host_info_with_finalizer( \
+    wasm_##name##_t*, void*, void (*)(void*));
+
+#define WASM_DECLARE_REF(name) \
+  WASM_DECLARE_REF_BASE(name) \
+  \
+  WASM_API_EXTERN wasm_ref_t* wasm_##name##_as_ref(wasm_##name##_t*); \
+  WASM_API_EXTERN wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t*); \
+  WASM_API_EXTERN const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t*); \
+  WASM_API_EXTERN const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t*);
+
+#define WASM_DECLARE_SHARABLE_REF(name) \
+  WASM_DECLARE_REF(name) \
+  WASM_DECLARE_OWN(shared_##name) \
+  \
+  WASM_API_EXTERN own wasm_shared_##name##_t* wasm_##name##_share(const wasm_##name##_t*); \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_obtain(wasm_store_t*, const wasm_shared_##name##_t*);
+
+
+WASM_DECLARE_REF_BASE(ref)
+
+
+// Frames
+
+WASM_DECLARE_OWN(frame)
+WASM_DECLARE_VEC(frame, *)
+WASM_API_EXTERN own wasm_frame_t* wasm_frame_copy(const wasm_frame_t*);
+
+WASM_API_EXTERN struct wasm_instance_t* wasm_frame_instance(const wasm_frame_t*);
+WASM_API_EXTERN uint32_t wasm_frame_func_index(const wasm_frame_t*);
+WASM_API_EXTERN size_t wasm_frame_func_offset(const wasm_frame_t*);
+WASM_API_EXTERN size_t wasm_frame_module_offset(const wasm_frame_t*);
+
+
+// Traps
+
+typedef wasm_name_t wasm_message_t;  // null terminated
+
+WASM_DECLARE_REF(trap)
+
+WASM_API_EXTERN own wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t*);
+
+WASM_API_EXTERN void wasm_trap_message(const wasm_trap_t*, own wasm_message_t* out);
+WASM_API_EXTERN own wasm_frame_t* wasm_trap_origin(const wasm_trap_t*);
+WASM_API_EXTERN void wasm_trap_trace(const wasm_trap_t*, own wasm_frame_vec_t* out);
+
+
+// Foreign Objects
+
+WASM_DECLARE_REF(foreign)
+
+WASM_API_EXTERN own wasm_foreign_t* wasm_foreign_new(wasm_store_t*);
+
+
+// Modules
+
+WASM_DECLARE_SHARABLE_REF(module)
+
+WASM_API_EXTERN own wasm_module_t* wasm_module_new(
+  wasm_store_t*, const wasm_byte_vec_t* binary);
+
+WASM_API_EXTERN bool wasm_module_validate(wasm_store_t*, const wasm_byte_vec_t* binary);
+
+WASM_API_EXTERN void wasm_module_imports(const wasm_module_t*, own wasm_importtype_vec_t* out);
+WASM_API_EXTERN void wasm_module_exports(const wasm_module_t*, own wasm_exporttype_vec_t* out);
+
+WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out);
+WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);
+
+
+// Function Instances
+
+WASM_DECLARE_REF(func)
+
+typedef own wasm_trap_t* (*wasm_func_callback_t)(
+  const wasm_val_vec_t* args, own wasm_val_vec_t* results);
+typedef own wasm_trap_t* (*wasm_func_callback_with_env_t)(
+  void* env, const wasm_val_vec_t* args, wasm_val_vec_t* results);
+
+WASM_API_EXTERN own wasm_func_t* wasm_func_new(
+  wasm_store_t*, const wasm_functype_t*, wasm_func_callback_t);
+WASM_API_EXTERN own wasm_func_t* wasm_func_new_with_env(
+  wasm_store_t*, const wasm_functype_t* type, wasm_func_callback_with_env_t,
+  void* env, void (*finalizer)(void*));
+
+WASM_API_EXTERN own wasm_functype_t* wasm_func_type(const wasm_func_t*);
+WASM_API_EXTERN size_t wasm_func_param_arity(const wasm_func_t*);
+WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*);
+
+WASM_API_EXTERN own wasm_trap_t* wasm_func_call(
+  const wasm_func_t*, const wasm_val_vec_t* args, wasm_val_vec_t* results);
+
+
+// Global Instances
+
+WASM_DECLARE_REF(global)
+
+WASM_API_EXTERN own wasm_global_t* wasm_global_new(
+  wasm_store_t*, const wasm_globaltype_t*, const wasm_val_t*);
+
+WASM_API_EXTERN own wasm_globaltype_t* wasm_global_type(const wasm_global_t*);
+
+WASM_API_EXTERN void wasm_global_get(const wasm_global_t*, own wasm_val_t* out);
+WASM_API_EXTERN void wasm_global_set(wasm_global_t*, const wasm_val_t*);
+
+
+// Table Instances
+
+WASM_DECLARE_REF(table)
+
+typedef uint32_t wasm_table_size_t;
+
+WASM_API_EXTERN own wasm_table_t* wasm_table_new(
+  wasm_store_t*, const wasm_tabletype_t*, wasm_ref_t* init);
+
+WASM_API_EXTERN own wasm_tabletype_t* wasm_table_type(const wasm_table_t*);
+
+WASM_API_EXTERN own wasm_ref_t* wasm_table_get(const wasm_table_t*, wasm_table_size_t index);
+WASM_API_EXTERN bool wasm_table_set(wasm_table_t*, wasm_table_size_t index, wasm_ref_t*);
+
+WASM_API_EXTERN wasm_table_size_t wasm_table_size(const wasm_table_t*);
+WASM_API_EXTERN bool wasm_table_grow(wasm_table_t*, wasm_table_size_t delta, wasm_ref_t* init);
+
+
+// Memory Instances
+
+WASM_DECLARE_REF(memory)
+
+typedef uint32_t wasm_memory_pages_t;
+
+static const size_t MEMORY_PAGE_SIZE = 0x10000;
+
+WASM_API_EXTERN own wasm_memory_t* wasm_memory_new(wasm_store_t*, const wasm_memorytype_t*);
+
+WASM_API_EXTERN own wasm_memorytype_t* wasm_memory_type(const wasm_memory_t*);
+
+WASM_API_EXTERN byte_t* wasm_memory_data(wasm_memory_t*);
+WASM_API_EXTERN size_t wasm_memory_data_size(const wasm_memory_t*);
+
+WASM_API_EXTERN wasm_memory_pages_t wasm_memory_size(const wasm_memory_t*);
+WASM_API_EXTERN bool wasm_memory_grow(wasm_memory_t*, wasm_memory_pages_t delta);
+
+
+// Externals
+
+WASM_DECLARE_REF(extern)
+WASM_DECLARE_VEC(extern, *)
+
+WASM_API_EXTERN wasm_externkind_t wasm_extern_kind(const wasm_extern_t*);
+WASM_API_EXTERN own wasm_externtype_t* wasm_extern_type(const wasm_extern_t*);
+
+WASM_API_EXTERN wasm_extern_t* wasm_func_as_extern(wasm_func_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_global_as_extern(wasm_global_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_table_as_extern(wasm_table_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_memory_as_extern(wasm_memory_t*);
+
+WASM_API_EXTERN wasm_func_t* wasm_extern_as_func(wasm_extern_t*);
+WASM_API_EXTERN wasm_global_t* wasm_extern_as_global(wasm_extern_t*);
+WASM_API_EXTERN wasm_table_t* wasm_extern_as_table(wasm_extern_t*);
+WASM_API_EXTERN wasm_memory_t* wasm_extern_as_memory(wasm_extern_t*);
+
+WASM_API_EXTERN const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t*);
+
+WASM_API_EXTERN const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_global_t* wasm_extern_as_global_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_memory_t* wasm_extern_as_memory_const(const wasm_extern_t*);
+
+
+// Module Instances
+
+WASM_DECLARE_REF(instance)
+
+WASM_API_EXTERN own wasm_instance_t* wasm_instance_new(
+  wasm_store_t*, const wasm_module_t*, const wasm_extern_vec_t* imports,
+  own wasm_trap_t**
+);
+
+WASM_API_EXTERN void wasm_instance_exports(const wasm_instance_t*, own wasm_extern_vec_t* out);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Convenience
+
+// Vectors
+
+#define WASM_EMPTY_VEC {0, NULL}
+#define WASM_ARRAY_VEC(array) {sizeof(array)/sizeof(*(array)), array}
+
+
+// Value Type construction short-hands
+
+static inline own wasm_valtype_t* wasm_valtype_new_i32(void) {
+  return wasm_valtype_new(WASM_I32);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_i64(void) {
+  return wasm_valtype_new(WASM_I64);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_f32(void) {
+  return wasm_valtype_new(WASM_F32);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_f64(void) {
+  return wasm_valtype_new(WASM_F64);
+}
+
+static inline own wasm_valtype_t* wasm_valtype_new_externref(void) {
+  return wasm_valtype_new(WASM_EXTERNREF);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_funcref(void) {
+  return wasm_valtype_new(WASM_FUNCREF);
+}
+
+
+// Function Types construction short-hands
+
+static inline own wasm_functype_t* wasm_functype_new_0_0(void) {
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_0(
+  own wasm_valtype_t* p
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_0(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_0(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_0_1(
+  own wasm_valtype_t* r
+) {
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_1(
+  own wasm_valtype_t* p, own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_1(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_1(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3,
+  own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_0_2(
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_2(
+  own wasm_valtype_t* p, own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_2(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2,
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_2(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3,
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+
+// Value construction short-hands
+
+static inline void wasm_val_init_ptr(own wasm_val_t* out, void* p) {
+#if UINTPTR_MAX == UINT32_MAX
+  out->kind = WASM_I32;
+  out->of.i32 = (intptr_t)p;
+#elif UINTPTR_MAX == UINT64_MAX
+  out->kind = WASM_I64;
+  out->of.i64 = (intptr_t)p;
+#endif
+}
+
+static inline void* wasm_val_ptr(const wasm_val_t* val) {
+#if UINTPTR_MAX == UINT32_MAX
+  return (void*)(intptr_t)val->of.i32;
+#elif UINTPTR_MAX == UINT64_MAX
+  return (void*)(intptr_t)val->of.i64;
+#endif
+}
+
+#define WASM_I32_VAL(i) {.kind = WASM_I32, .of = {.i32 = i}}
+#define WASM_I64_VAL(i) {.kind = WASM_I64, .of = {.i64 = i}}
+#define WASM_F32_VAL(z) {.kind = WASM_F32, .of = {.f32 = z}}
+#define WASM_F64_VAL(z) {.kind = WASM_F64, .of = {.f64 = z}}
+#define WASM_REF_VAL(r) {.kind = WASM_EXTERNREF, .of = {.ref = r}}
+#define WASM_INIT_VAL {.kind = WASM_EXTERNREF, .of = {.ref = NULL}}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef own
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // #ifdef WASM_H
diff --git a/crates/c-api/include/wasm.hh b/crates/c-api/include/wasm.hh
new file mode 100644
index 000000000..e03408241
--- /dev/null
+++ b/crates/c-api/include/wasm.hh
@@ -0,0 +1,849 @@
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <https://github.com/WebAssembly/wasm-c-api/blob/2ce1367c9d1271c83fb63bef26d896a2f290cd23/include/wasm.hh>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+// WebAssembly C++ API
+
+#ifndef WASM_HH
+#define WASM_HH
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <limits>
+#include <string>
+
+#ifndef WASM_API_EXTERN
+#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC)
+#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
+#endif
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Auxiliaries
+
+// Machine types
+
+static_assert(sizeof(float) == sizeof(int32_t), "incompatible float type");
+static_assert(sizeof(double) == sizeof(int64_t), "incompatible double type");
+static_assert(sizeof(intptr_t) == sizeof(int32_t) ||
+              sizeof(intptr_t) == sizeof(int64_t), "incompatible pointer type");
+
+using byte_t = char;
+using float32_t = float;
+using float64_t = double;
+
+
+namespace wasm {
+
+// Vectors
+
+template<class T>
+class vec {
+  static const size_t invalid_size = SIZE_MAX;
+
+  size_t size_;
+  std::unique_ptr<T[]> data_;
+
+#ifdef WASM_API_DEBUG
+  WASM_API_EXTERN void make_data();
+  WASM_API_EXTERN void free_data();
+#else
+  void make_data() {}
+  void free_data() {}
+#endif
+
+  vec(size_t size) : vec(size, size ? new(std::nothrow) T[size] : nullptr) {
+    make_data();
+  }
+
+  vec(size_t size, T* data) : size_(size), data_(data) {
+    assert(!!size_ == !!data_ || size_ == invalid_size);
+  }
+
+public:
+  using elem_type = T;
+
+  vec(vec<T>&& that) : vec(that.size_, that.data_.release()) {
+    that.size_ = invalid_size;
+  }
+
+  ~vec() {
+    free_data();
+  }
+
+  operator bool() const {
+    return bool(size_ != invalid_size);
+  }
+
+  auto size() const -> size_t {
+    return size_;
+  }
+
+  auto get() const -> const T* {
+    return data_.get();
+  }
+
+  auto get() -> T* {
+    return data_.get();
+  }
+
+  auto release() -> T* {
+    size_ = invalid_size;
+    return data_.release();
+  }
+
+  void reset() {
+    free_data();
+    size_ = invalid_size;
+    data_.reset();
+  }
+
+  void reset(vec& that) {
+    free_data();
+    size_ = that.size_;
+    data_.reset(that.data_.release());
+    that.size_ = invalid_size;
+  }
+
+  auto operator=(vec&& that) -> vec& {
+    reset(that);
+    return *this;
+  }
+
+  auto operator[](size_t i) -> T& {
+    assert(i < size_);
+    return data_[i];
+  }
+
+  auto operator[](size_t i) const -> const T& {
+    assert(i < size_);
+    return data_[i];
+  }
+
+  auto copy() const -> vec {
+    auto v = vec(size_);
+    if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i];
+    return v;
+  }
+
+  // TODO: This can't be used for e.g. vec<Val>
+  auto deep_copy() const -> vec {
+    auto v = vec(size_);
+    if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]->copy();
+    return v;
+  }
+
+  static auto make_uninitialized(size_t size = 0) -> vec {
+    return vec(size);
+  }
+
+  static auto make(size_t size, T init[]) -> vec {
+    auto v = vec(size);
+    if (v) for (size_t i = 0; i < size; ++i) v.data_[i] = std::move(init[i]);
+    return v;
+  }
+
+  static auto make(std::string s) -> vec<char> {
+    auto v = vec(s.length());
+    if (v) std::strncpy(v.get(), s.data(), s.length());
+    return v;
+  }
+
+  static auto make_nt(std::string s) -> vec<char> {
+    auto v = vec(s.length() + 1);
+    if (v) std::strcpy(v.get(), s.data());
+    return v;
+  }
+
+  // TODO(mvsc): MVSC requires this special case:
+  static auto make() -> vec {
+    return vec(0);
+  }
+
+  template<class... Ts>
+  static auto make(Ts&&... args) -> vec {
+    T data[] = { std::forward<Ts>(args)... };
+    return make(sizeof...(Ts), data);
+  }
+
+  static auto adopt(size_t size, T data[]) -> vec {
+    return vec(size, data);
+  }
+
+  static auto invalid() -> vec {
+    return vec(invalid_size, nullptr);
+  }
+};
+
+
+// Ownership
+
+class destroyer {
+public:
+  template <typename T>
+  void operator()(T* ptr) {
+    ptr->destroy();
+  }
+};
+
+template<class T> using own = std::unique_ptr<T, destroyer>;
+template<class T> using ownvec = vec<own<T>>;
+
+template<class T> own<T> make_own(T* ptr) {
+  return own<T>(ptr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Environment
+
+// Configuration
+
+class WASM_API_EXTERN Config {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Config() = default;
+  ~Config() = default;
+
+public:
+  static auto make() -> own<Config>;
+
+  // Implementations may provide custom methods for manipulating Configs.
+};
+
+
+// Engine
+
+class WASM_API_EXTERN Engine {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Engine() = default;
+  ~Engine() = default;
+
+public:
+  static auto make(own<Config>&& = Config::make()) -> own<Engine>;
+};
+
+
+// Store
+
+class WASM_API_EXTERN Store {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Store() = default;
+  ~Store() = default;
+
+public:
+  static auto make(Engine*) -> own<Store>;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Type Representations
+
+// Type attributes
+
+enum class Mutability : uint8_t { CONST, VAR };
+
+struct Limits {
+  uint32_t min;
+  uint32_t max;
+
+  Limits(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) :
+    min(min), max(max) {}
+};
+
+
+// Value Types
+
+enum class ValKind : uint8_t {
+  I32, I64, F32, F64,
+  EXTERNREF = 128, FUNCREF,
+};
+
+inline bool is_num(ValKind k) { return k < ValKind::EXTERNREF; }
+inline bool is_ref(ValKind k) { return k >= ValKind::EXTERNREF; }
+
+
+class WASM_API_EXTERN ValType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ValType() = default;
+  ~ValType() = default;
+
+public:
+  static auto make(ValKind) -> own<ValType>;
+  auto copy() const -> own<ValType>;
+
+  auto kind() const -> ValKind;
+  auto is_num() const -> bool { return wasm::is_num(kind()); }
+  auto is_ref() const -> bool { return wasm::is_ref(kind()); }
+};
+
+
+// External Types
+
+enum class ExternKind : uint8_t {
+  FUNC, GLOBAL, TABLE, MEMORY
+};
+
+class FuncType;
+class GlobalType;
+class TableType;
+class MemoryType;
+
+class WASM_API_EXTERN ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ExternType() = default;
+  ~ExternType() = default;
+
+public:
+  auto copy() const-> own<ExternType>;
+
+  auto kind() const -> ExternKind;
+
+  auto func() -> FuncType*;
+  auto global() -> GlobalType*;
+  auto table() -> TableType*;
+  auto memory() -> MemoryType*;
+
+  auto func() const -> const FuncType*;
+  auto global() const -> const GlobalType*;
+  auto table() const -> const TableType*;
+  auto memory() const -> const MemoryType*;
+};
+
+
+// Function Types
+
+class WASM_API_EXTERN FuncType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  FuncType() = default;
+  ~FuncType() = default;
+
+public:
+  static auto make(
+    ownvec<ValType>&& params = ownvec<ValType>::make(),
+    ownvec<ValType>&& results = ownvec<ValType>::make()
+  ) -> own<FuncType>;
+
+  auto copy() const -> own<FuncType>;
+
+  auto params() const -> const ownvec<ValType>&;
+  auto results() const -> const ownvec<ValType>&;
+};
+
+
+// Global Types
+
+class WASM_API_EXTERN GlobalType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  GlobalType() = default;
+  ~GlobalType() = default;
+
+public:
+  static auto make(own<ValType>&&, Mutability) -> own<GlobalType>;
+  auto copy() const -> own<GlobalType>;
+
+  auto content() const -> const ValType*;
+  auto mutability() const -> Mutability;
+};
+
+
+// Table Types
+
+class WASM_API_EXTERN TableType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  TableType() = default;
+  ~TableType() = default;
+
+public:
+  static auto make(own<ValType>&&, Limits) -> own<TableType>;
+  auto copy() const -> own<TableType>;
+
+  auto element() const -> const ValType*;
+  auto limits() const -> const Limits&;
+};
+
+
+// Memory Types
+
+class WASM_API_EXTERN MemoryType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  MemoryType() = default;
+  ~MemoryType() = default;
+
+public:
+  static auto make(Limits) -> own<MemoryType>;
+  auto copy() const -> own<MemoryType>;
+
+  auto limits() const -> const Limits&;
+};
+
+
+// Import Types
+
+using Name = vec<byte_t>;
+
+class WASM_API_EXTERN ImportType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ImportType() = default;
+  ~ImportType() = default;
+
+public:
+  static auto make(Name&& module, Name&& name, own<ExternType>&&) ->
+    own<ImportType>;
+  auto copy() const -> own<ImportType>;
+
+  auto module() const -> const Name&;
+  auto name() const -> const Name&;
+  auto type() const -> const ExternType*;
+};
+
+
+// Export Types
+
+class WASM_API_EXTERN ExportType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ExportType() = default;
+  ~ExportType() = default;
+
+public:
+  static auto make(Name&&, own<ExternType>&&) -> own<ExportType>;
+  auto copy() const -> own<ExportType>;
+
+  auto name() const -> const Name&;
+  auto type() const -> const ExternType*;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Objects
+
+// References
+
+class WASM_API_EXTERN Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Ref() = default;
+  ~Ref() = default;
+
+public:
+  auto copy() const -> own<Ref>;
+  auto same(const Ref*) const -> bool;
+
+  auto get_host_info() const -> void*;
+  void set_host_info(void* info, void (*finalizer)(void*) = nullptr);
+};
+
+
+// Values
+
+class Val {
+  ValKind kind_;
+  union impl {
+    int32_t i32;
+    int64_t i64;
+    float32_t f32;
+    float64_t f64;
+    Ref* ref;
+  } impl_;
+
+  Val(ValKind kind, impl impl) : kind_(kind), impl_(impl) {}
+
+public:
+  Val() : kind_(ValKind::EXTERNREF) { impl_.ref = nullptr; }
+  explicit Val(int32_t i) : kind_(ValKind::I32) { impl_.i32 = i; }
+  explicit Val(int64_t i) : kind_(ValKind::I64) { impl_.i64 = i; }
+  explicit Val(float32_t z) : kind_(ValKind::F32) { impl_.f32 = z; }
+  explicit Val(float64_t z) : kind_(ValKind::F64) { impl_.f64 = z; }
+  explicit Val(own<Ref>&& r) : kind_(ValKind::EXTERNREF) { impl_.ref = r.release(); }
+
+  Val(Val&& that) : kind_(that.kind_), impl_(that.impl_) {
+    if (is_ref()) that.impl_.ref = nullptr;
+  }
+
+  ~Val() {
+    reset();
+  }
+
+  auto is_num() const -> bool { return wasm::is_num(kind_); }
+  auto is_ref() const -> bool { return wasm::is_ref(kind_); }
+
+  static auto i32(int32_t x) -> Val { return Val(x); }
+  static auto i64(int64_t x) -> Val { return Val(x); }
+  static auto f32(float32_t x) -> Val { return Val(x); }
+  static auto f64(float64_t x) -> Val { return Val(x); }
+  static auto ref(own<Ref>&& x) -> Val { return Val(std::move(x)); }
+  template<class T> inline static auto make(T x) -> Val;
+  template<class T> inline static auto make(own<T>&& x) -> Val;
+
+  void reset() {
+    if (is_ref() && impl_.ref) {
+      destroyer()(impl_.ref);
+      impl_.ref = nullptr;
+    }
+  }
+
+  void reset(Val& that) {
+    reset();
+    kind_ = that.kind_;
+    impl_ = that.impl_;
+    if (is_ref()) that.impl_.ref = nullptr;
+  }
+
+  auto operator=(Val&& that) -> Val& {
+    reset(that);
+    return *this;
+  }
+
+  auto kind() const -> ValKind { return kind_; }
+  auto i32() const -> int32_t { assert(kind_ == ValKind::I32); return impl_.i32; }
+  auto i64() const -> int64_t { assert(kind_ == ValKind::I64); return impl_.i64; }
+  auto f32() const -> float32_t { assert(kind_ == ValKind::F32); return impl_.f32; }
+  auto f64() const -> float64_t { assert(kind_ == ValKind::F64); return impl_.f64; }
+  auto ref() const -> Ref* { assert(is_ref()); return impl_.ref; }
+  template<class T> inline auto get() const -> T;
+
+  auto release_ref() -> own<Ref> {
+    assert(is_ref());
+    auto ref = impl_.ref;
+    impl_.ref = nullptr;
+    return own<Ref>(ref);
+  }
+
+  auto copy() const -> Val {
+    if (is_ref() && impl_.ref != nullptr) {
+      // TODO(mvsc): MVSC cannot handle this:
+      // impl impl = {.ref = impl_.ref->copy().release()};
+      impl impl;
+      impl.ref = impl_.ref->copy().release();
+      return Val(kind_, impl);
+    } else {
+      return Val(kind_, impl_);
+    }
+  }
+};
+
+
+template<> inline auto Val::make<int32_t>(int32_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<int64_t>(int64_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<float32_t>(float32_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<float64_t>(float64_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<Ref>(own<Ref>&& x) -> Val {
+  return Val(std::move(x));
+}
+
+template<> inline auto Val::make<uint32_t>(uint32_t x) -> Val {
+  return Val(static_cast<int32_t>(x));
+}
+template<> inline auto Val::make<uint64_t>(uint64_t x) -> Val {
+  return Val(static_cast<int64_t>(x));
+}
+
+template<> inline auto Val::get<int32_t>() const -> int32_t { return i32(); }
+template<> inline auto Val::get<int64_t>() const -> int64_t { return i64(); }
+template<> inline auto Val::get<float32_t>() const -> float32_t { return f32(); }
+template<> inline auto Val::get<float64_t>() const -> float64_t { return f64(); }
+template<> inline auto Val::get<Ref*>() const -> Ref* { return ref(); }
+
+template<> inline auto Val::get<uint32_t>() const -> uint32_t {
+  return static_cast<uint32_t>(i32());
+}
+template<> inline auto Val::get<uint64_t>() const -> uint64_t {
+  return static_cast<uint64_t>(i64());
+}
+
+
+// Traps
+
+using Message = vec<byte_t>;  // null terminated
+
+class Instance;
+
+class WASM_API_EXTERN Frame {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Frame() = default;
+  ~Frame() = default;
+
+public:
+  auto copy() const -> own<Frame>;
+
+  auto instance() const -> Instance*;
+  auto func_index() const -> uint32_t;
+  auto func_offset() const -> size_t;
+  auto module_offset() const -> size_t;
+};
+
+class WASM_API_EXTERN Trap : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Trap() = default;
+  ~Trap() = default;
+
+public:
+  static auto make(Store*, const Message& msg) -> own<Trap>;
+  auto copy() const -> own<Trap>;
+
+  auto message() const -> Message;
+  auto origin() const -> own<Frame>;  // may be null
+  auto trace() const -> ownvec<Frame>;  // may be empty, origin first
+};
+
+
+// Modules
+
+template<class T> class WASM_API_EXTERN Shared;
+
+class WASM_API_EXTERN Module : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Module() = default;
+  ~Module() = default;
+
+public:
+  static auto validate(Store*, const vec<byte_t>& binary) -> bool;
+  static auto make(Store*, const vec<byte_t>& binary) -> own<Module>;
+  auto copy() const -> own<Module>;
+
+  auto imports() const -> ownvec<ImportType>;
+  auto exports() const -> ownvec<ExportType>;
+
+  auto share() const -> own<Shared<Module>>;
+  static auto obtain(Store*, const Shared<Module>*) -> own<Module>;
+
+  auto serialize() const -> vec<byte_t>;
+  static auto deserialize(Store*, const vec<byte_t>&) -> own<Module>;
+};
+
+
+// Shared objects
+
+template<>
+class WASM_API_EXTERN Shared<Module> {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Shared() = default;
+  ~Shared() = default;
+};
+
+
+// Foreign Objects
+
+class WASM_API_EXTERN Foreign : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Foreign() = default;
+  ~Foreign() = default;
+
+public:
+  static auto make(Store*) -> own<Foreign>;
+  auto copy() const -> own<Foreign>;
+};
+
+
+// Externals
+
+class Func;
+class Global;
+class Table;
+class Memory;
+
+class WASM_API_EXTERN Extern : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Extern() = default;
+  ~Extern() = default;
+
+public:
+  auto copy() const -> own<Extern>;
+
+  auto kind() const -> ExternKind;
+  auto type() const -> own<ExternType>;
+
+  auto func() -> Func*;
+  auto global() -> Global*;
+  auto table() -> Table*;
+  auto memory() -> Memory*;
+
+  auto func() const -> const Func*;
+  auto global() const -> const Global*;
+  auto table() const -> const Table*;
+  auto memory() const -> const Memory*;
+};
+
+
+// Function Instances
+
+class WASM_API_EXTERN Func : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Func() = default;
+  ~Func() = default;
+
+public:
+  using callback = auto (*)(const vec<Val>&, vec<Val>&) -> own<Trap>;
+  using callback_with_env = auto (*)(void*, const vec<Val>&, vec<Val>&) -> own<Trap>;
+
+  static auto make(Store*, const FuncType*, callback) -> own<Func>;
+  static auto make(Store*, const FuncType*, callback_with_env,
+    void*, void (*finalizer)(void*) = nullptr) -> own<Func>;
+  auto copy() const -> own<Func>;
+
+  auto type() const -> own<FuncType>;
+  auto param_arity() const -> size_t;
+  auto result_arity() const -> size_t;
+
+  auto call(const vec<Val>&, vec<Val>&) const -> own<Trap>;
+};
+
+
+// Global Instances
+
+class WASM_API_EXTERN Global : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Global() = default;
+  ~Global() = default;
+
+ public:
+  static auto make(Store*, const GlobalType*, const Val&) -> own<Global>;
+  auto copy() const -> own<Global>;
+
+  auto type() const -> own<GlobalType>;
+  auto get() const -> Val;
+  void set(const Val&);
+};
+
+
+// Table Instances
+
+class WASM_API_EXTERN Table : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Table() = default;
+  ~Table() = default;
+
+public:
+  using size_t = uint32_t;
+
+  static auto make(
+    Store*, const TableType*, const Ref* init = nullptr) -> own<Table>;
+  auto copy() const -> own<Table>;
+
+  auto type() const -> own<TableType>;
+  auto get(size_t index) const -> own<Ref>;
+  auto set(size_t index, const Ref*) -> bool;
+  auto size() const -> size_t;
+  auto grow(size_t delta, const Ref* init = nullptr) -> bool;
+};
+
+
+// Memory Instances
+
+class WASM_API_EXTERN Memory : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Memory() = default;
+  ~Memory() = default;
+
+public:
+  static auto make(Store*, const MemoryType*) -> own<Memory>;
+  auto copy() const -> own<Memory>;
+
+  using pages_t = uint32_t;
+
+  static const size_t page_size = 0x10000;
+
+  auto type() const -> own<MemoryType>;
+  auto data() const -> byte_t*;
+  auto data_size() const -> size_t;
+  auto size() const -> pages_t;
+  auto grow(pages_t delta) -> bool;
+};
+
+
+// Module Instances
+
+class WASM_API_EXTERN Instance : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Instance() = default;
+  ~Instance() = default;
+
+public:
+  static auto make(
+    Store*, const Module*, const vec<Extern*>&, own<Trap>* = nullptr
+  ) -> own<Instance>;
+  auto copy() const -> own<Instance>;
+
+  auto exports() const -> ownvec<Extern>;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace wasm
+
+#endif  // #ifdef WASM_HH
diff --git a/crates/c-api/wasm-c-api b/crates/c-api/wasm-c-api
deleted file mode 160000
index c9d312846..000000000
--- a/crates/c-api/wasm-c-api
+++ /dev/null
@@ -1,1 +0,0 @@
-Subproject commit c9d31284651b975f05ac27cee0bab1377560b87e
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 821636a53..d76dc9b23 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -252,7 +252,9 @@ wasmtime_option_group! {
 wasmtime_option_group! {
     #[derive(PartialEq, Clone)]
     pub struct WasiOptions {
-        /// Enable support for WASI common APIs
+        /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random.
+        pub cli: Option<bool>,
+        /// Deprecated alias for `cli`
         pub common: Option<bool>,
         /// Enable suport for WASI neural network API (experimental)
         pub nn: Option<bool>,
@@ -265,7 +267,7 @@ wasmtime_option_group! {
         pub listenfd: Option<bool>,
         /// Grant access to the given TCP listen socket
         pub tcplisten: Vec<String>,
-        /// Implement WASI with preview2 primitives (experimental).
+        /// Implement WASI CLI APIs with preview2 primitives (experimental).
         ///
         /// Indicates that the implementation of WASI preview1 should be backed by
         /// the preview2 implementation for components.
@@ -293,6 +295,10 @@ wasmtime_option_group! {
         pub udp: Option<bool>,
         /// Allows imports from the `wasi_unstable` core wasm module.
         pub preview0: Option<bool>,
+        /// Inherit all environment variables from the parent process.
+        ///
+        /// This option can be further overwritten with `--env` flags.
+        pub inherit_env: Option<bool>,
     }
 
     enum Wasi {
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index b093e666f..cf2b96f15 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -13,15 +13,15 @@ use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap};
 use cranelift_frontend::FunctionBuilder;
 use cranelift_frontend::Variable;
 use cranelift_wasm::{
-    FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap, HeapData, HeapStyle,
-    MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment, TypeIndex, WasmHeapType,
-    WasmRefType, WasmResult, WasmValType,
+    EngineOrModuleTypeIndex, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap,
+    HeapData, HeapStyle, MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment,
+    TypeIndex, WasmHeapType, WasmRefType, WasmResult, WasmValType,
 };
 use std::mem;
 use wasmparser::Operator;
 use wasmtime_environ::{
-    BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypesBuilder,
-    PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE,
+    BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleType,
+    ModuleTypesBuilder, PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE,
 };
 use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};
 
@@ -955,13 +955,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
         // contents, we check for a null entry here, and
         // if null, we take a slow-path that invokes a
         // libcall.
-        let table_entry_addr = table_data.prepare_table_addr(
+        let (table_entry_addr, flags) = table_data.prepare_table_addr(
             builder,
             index,
             pointer_type,
             self.isa.flags().enable_table_access_spectre_mitigation(),
         );
-        let flags = ir::MemFlags::trusted().with_table();
         let value = builder.ins().load(pointer_type, flags, table_entry_addr, 0);
         // Mask off the "initialized bit". See documentation on
         // FUNCREF_INIT_BIT in crates/environ/src/ref_bits.rs for more
@@ -1038,6 +1037,87 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
             .get(&func_index)
             .map(|s| *s)
     }
+
+    /// Proof-carrying code: create a memtype describing an empty
+    /// runtime struct (to be updated later).
+    fn create_empty_struct_memtype(&self, func: &mut ir::Function) -> ir::MemoryType {
+        func.create_memory_type(ir::MemoryTypeData::Struct {
+            size: 0,
+            fields: vec![],
+        })
+    }
+
+    /// Proof-carrying code: add a new field to a memtype used to
+    /// describe a runtime struct. A memory region of type `memtype`
+    /// will have a pointer at `offset` pointing to another memory
+    /// region of type `pointee`. `readonly` indicates whether the
+    /// PCC-checked code is expected to update this field or not.
+    fn add_field_to_memtype(
+        &self,
+        func: &mut ir::Function,
+        memtype: ir::MemoryType,
+        offset: u32,
+        pointee: ir::MemoryType,
+        readonly: bool,
+    ) {
+        let ptr_size = self.pointer_type().bytes();
+        match &mut func.memory_types[memtype] {
+            ir::MemoryTypeData::Struct { size, fields } => {
+                *size = std::cmp::max(*size, offset.checked_add(ptr_size).unwrap().into());
+                fields.push(ir::MemoryTypeField {
+                    ty: self.pointer_type(),
+                    offset: offset.into(),
+                    readonly,
+                    fact: Some(ir::Fact::Mem {
+                        ty: pointee,
+                        min_offset: 0,
+                        max_offset: 0,
+                        nullable: false,
+                    }),
+                });
+
+                // Sort fields by offset -- we need to do this now
+                // because we may create an arbitrary number of
+                // memtypes for imported memories and we don't
+                // otherwise track them.
+                fields.sort_by_key(|f| f.offset);
+            }
+            _ => panic!("Cannot add field to non-struct memtype"),
+        }
+    }
+
+    /// Add one level of indirection to a pointer-and-memtype pair:
+    /// generate a load in the code at the specified offset, and if
+    /// memtypes are in use, add a field to the original struct and
+    /// generate a new memtype for the pointee.
+    fn load_pointer_with_memtypes(
+        &self,
+        func: &mut ir::Function,
+        value: ir::GlobalValue,
+        offset: u32,
+        readonly: bool,
+        memtype: Option<ir::MemoryType>,
+    ) -> (ir::GlobalValue, Option<ir::MemoryType>) {
+        let pointee = func.create_global_value(ir::GlobalValueData::Load {
+            base: value,
+            offset: Offset32::new(i32::try_from(offset).unwrap()),
+            global_type: self.pointer_type(),
+            flags: MemFlags::trusted().with_readonly(),
+        });
+
+        let mt = memtype.map(|mt| {
+            let pointee_mt = self.create_empty_struct_memtype(func);
+            self.add_field_to_memtype(func, mt, offset, pointee_mt, readonly);
+            func.global_value_facts[pointee] = Some(Fact::Mem {
+                ty: pointee_mt,
+                min_offset: 0,
+                max_offset: 0,
+                nullable: false,
+            });
+            pointee_mt
+        });
+        (pointee, mt)
+    }
 }
 
 struct Call<'a, 'func, 'module_env> {
@@ -1046,6 +1126,16 @@ struct Call<'a, 'func, 'module_env> {
     tail: bool,
 }
 
+enum CheckIndirectCallTypeSignature {
+    Runtime,
+    StaticMatch {
+        /// Whether or not the funcref may be null or if it's statically known
+        /// to not be null.
+        may_be_null: bool,
+    },
+    StaticTrap,
+}
+
 impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
     /// Create a new `Call` site that will do regular, non-tail calls.
     pub fn new(
@@ -1147,65 +1237,160 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
-        let pointer_type = self.env.pointer_type();
-
+    ) -> WasmResult<Option<ir::Inst>> {
         // Get the funcref pointer from the table.
         let funcref_ptr =
             self.env
                 .get_or_init_func_ref_table_elem(self.builder, table_index, callee);
 
-        // Check for whether the table element is null, and trap if so.
-        self.builder
-            .ins()
-            .trapz(funcref_ptr, ir::TrapCode::IndirectCallToNull);
-
         // If necessary, check the signature.
-        match self.env.module.table_plans[table_index].style {
-            TableStyle::CallerChecksSignature => {
-                let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
-                let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
-                let vmctx = self.env.vmctx(self.builder.func);
-                let base = self.builder.ins().global_value(pointer_type, vmctx);
-
-                // Load the caller ID. This requires loading the `*mut
-                // VMFuncRef` base pointer from `VMContext` and then loading,
-                // based on `SignatureIndex`, the corresponding entry.
-                let mem_flags = ir::MemFlags::trusted().with_readonly();
-                let signatures = self.builder.ins().load(
-                    pointer_type,
-                    mem_flags,
-                    base,
-                    i32::try_from(self.env.offsets.vmctx_type_ids_array()).unwrap(),
-                );
-                let sig_index = self.env.module.types[ty_index].unwrap_function();
-                let offset =
-                    i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap())
-                        .unwrap();
-                let caller_sig_id =
-                    self.builder
-                        .ins()
-                        .load(sig_id_type, mem_flags, signatures, offset);
-
-                // Load the callee ID.
-                let mem_flags = ir::MemFlags::trusted().with_readonly();
-                let callee_sig_id = self.builder.ins().load(
-                    sig_id_type,
-                    mem_flags,
-                    funcref_ptr,
-                    i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
-                );
+        let check = self.check_indirect_call_type_signature(table_index, ty_index, funcref_ptr);
+
+        let trap_code = match check {
+            // `funcref_ptr` is checked at runtime that its type matches,
+            // meaning that if code gets this far it's guaranteed to not be
+            // null. That means nothing in `unchecked_call` can fail.
+            CheckIndirectCallTypeSignature::Runtime => None,
+
+            // No type check was performed on `funcref_ptr` because it's
+            // statically known to have the right type. Note that whether or
+            // not the function is null is not necessarily tested so far since
+            // no type information was inspected.
+            //
+            // If the table may hold null functions, then further loads in
+            // `unchecked_call` may fail. If the table only holds non-null
+            // functions, though, then there's no possibility of a trap.
+            CheckIndirectCallTypeSignature::StaticMatch { may_be_null } => {
+                if may_be_null {
+                    Some(ir::TrapCode::IndirectCallToNull)
+                } else {
+                    None
+                }
+            }
 
-                // Check that they match.
-                let cmp = self
-                    .builder
-                    .ins()
-                    .icmp(IntCC::Equal, callee_sig_id, caller_sig_id);
-                self.builder.ins().trapz(cmp, ir::TrapCode::BadSignature);
+            // Code has already trapped, so return nothing indicating that this
+            // is now unreachable code.
+            CheckIndirectCallTypeSignature::StaticTrap => return Ok(None),
+        };
+        self.unchecked_call(sig_ref, funcref_ptr, trap_code, call_args)
+            .map(Some)
+    }
+
+    fn check_indirect_call_type_signature(
+        &mut self,
+        table_index: TableIndex,
+        ty_index: TypeIndex,
+        funcref_ptr: ir::Value,
+    ) -> CheckIndirectCallTypeSignature {
+        let pointer_type = self.env.pointer_type();
+        let table = &self.env.module.table_plans[table_index];
+        let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
+        let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
+
+        // Generate a rustc compile error here if more styles are added in
+        // the future as the following code is tailored to just this style.
+        let TableStyle::CallerChecksSignature = table.style;
+
+        // Test if a type check is necessary for this table. If this table is a
+        // table of typed functions and that type matches `ty_index`, then
+        // there's no need to perform a typecheck.
+        match table.table.wasm_ty.heap_type {
+            // Functions do not have a statically known type in the table, a
+            // typecheck is required. Fall through to below to perform the
+            // actual typecheck.
+            WasmHeapType::Func => {}
+
+            // Functions that have a statically known type are either going to
+            // always succeed or always fail. Figure out by inspecting the types
+            // further.
+            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(table_ty)) => {
+                // If `ty_index` matches `table_ty`, then this call is
+                // statically known to have the right type, so no checks are
+                // necessary.
+                let ModuleType::Function(specified_ty) = self.env.module.types[ty_index];
+                if specified_ty == table_ty {
+                    return CheckIndirectCallTypeSignature::StaticMatch {
+                        may_be_null: table.table.wasm_ty.nullable,
+                    };
+                }
+
+                // Otherwise if the types don't match then either (a) this is a
+                // null pointer or (b) it's a pointer with the wrong type.
+                // Figure out which and trap here.
+                //
+                // If it's possible to have a null here then try to load the
+                // type information. If that fails due to the function being a
+                // null pointer, then this was a call to null. Otherwise if it
+                // succeeds then we know it won't match, so trap anyway.
+                if table.table.wasm_ty.nullable {
+                    let mem_flags = ir::MemFlags::trusted().with_readonly();
+                    self.builder.ins().load(
+                        sig_id_type,
+                        mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
+                        funcref_ptr,
+                        i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
+                    );
+                }
+                self.builder.ins().trap(ir::TrapCode::BadSignature);
+                return CheckIndirectCallTypeSignature::StaticTrap;
+            }
+
+            // Tables of `nofunc` can only be inhabited by null, so go ahead and
+            // trap with that.
+            WasmHeapType::NoFunc => {
+                assert!(table.table.wasm_ty.nullable);
+                self.builder.ins().trap(ir::TrapCode::IndirectCallToNull);
+                return CheckIndirectCallTypeSignature::StaticTrap;
+            }
+
+            // Engine-indexed types don't show up until runtime and it's a wasm
+            // validation error to perform a call through an `externref` table,
+            // so these cases are dynamically not reachable.
+            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(_)) | WasmHeapType::Extern => {
+                unreachable!()
             }
         }
 
-        self.unchecked_call(sig_ref, funcref_ptr, call_args)
+        let vmctx = self.env.vmctx(self.builder.func);
+        let base = self.builder.ins().global_value(pointer_type, vmctx);
+
+        // Load the caller ID. This requires loading the `*mut VMFuncRef` base
+        // pointer from `VMContext` and then loading, based on `SignatureIndex`,
+        // the corresponding entry.
+        let mem_flags = ir::MemFlags::trusted().with_readonly();
+        let signatures = self.builder.ins().load(
+            pointer_type,
+            mem_flags,
+            base,
+            i32::try_from(self.env.offsets.vmctx_type_ids_array()).unwrap(),
+        );
+        let sig_index = self.env.module.types[ty_index].unwrap_function();
+        let offset =
+            i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap()).unwrap();
+        let caller_sig_id = self
+            .builder
+            .ins()
+            .load(sig_id_type, mem_flags, signatures, offset);
+
+        // Load the callee ID.
+        //
+        // Note that the callee may be null in which case this load may
+        // trap. If so use the `IndirectCallToNull` trap code.
+        let mem_flags = ir::MemFlags::trusted().with_readonly();
+        let callee_sig_id = self.builder.ins().load(
+            sig_id_type,
+            mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
+            funcref_ptr,
+            i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
+        );
+
+        // Check that they match.
+        let cmp = self
+            .builder
+            .ins()
+            .icmp(IntCC::Equal, callee_sig_id, caller_sig_id);
+        self.builder.ins().trapz(cmp, ir::TrapCode::BadSignature);
+        CheckIndirectCallTypeSignature::Runtime
     }
 
     /// Call a typed function reference.
@@ -1215,19 +1400,15 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         callee: ir::Value,
         args: &[ir::Value],
     ) -> WasmResult<ir::Inst> {
-        // Check for whether the callee is null, and trap if so.
-        //
         // FIXME: the wasm type system tracks enough information to know whether
         // `callee` is a null reference or not. In some situations it can be
         // statically known here that `callee` cannot be null in which case this
-        // null check can be elided. This requires feeding type information from
+        // can be `None` instead. This requires feeding type information from
         // wasmparser's validator into this function, however, which is not
         // easily done at this time.
-        self.builder
-            .ins()
-            .trapz(callee, ir::TrapCode::NullReference);
+        let callee_load_trap_code = Some(ir::TrapCode::NullReference);
 
-        self.unchecked_call(sig_ref, callee, args)
+        self.unchecked_call(sig_ref, callee, callee_load_trap_code, args)
     }
 
     /// This calls a function by reference without checking the signature.
@@ -1239,15 +1420,22 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         &mut self,
         sig_ref: ir::SigRef,
         callee: ir::Value,
+        callee_load_trap_code: Option<ir::TrapCode>,
         call_args: &[ir::Value],
     ) -> WasmResult<ir::Inst> {
         let pointer_type = self.env.pointer_type();
 
         // Dereference callee pointer to get the function address.
+        //
+        // Note that this may trap if `callee` hasn't previously been verified
+        // to be non-null. This means that this load is annotated with an
+        // optional trap code provided by the caller of `unchecked_call` which
+        // will handle the case where this is either already known to be
+        // non-null or may trap.
         let mem_flags = ir::MemFlags::trusted().with_readonly();
         let func_addr = self.builder.ins().load(
             pointer_type,
-            mem_flags,
+            mem_flags.with_trap_code(callee_load_trap_code),
             callee,
             i32::from(self.env.offsets.ptr.vm_func_ref_wasm_call()),
         );
@@ -1440,13 +1628,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                 // Load the table element.
                 self.ensure_table_exists(builder.func, table_index);
                 let table_data = self.tables[table_index].as_ref().unwrap();
-                let elem_addr = table_data.prepare_table_addr(
+                let (elem_addr, flags) = table_data.prepare_table_addr(
                     builder,
                     index,
                     pointer_type,
                     self.isa.flags().enable_table_access_spectre_mitigation(),
                 );
-                let flags = ir::MemFlags::trusted().with_table();
                 let elem = builder.ins().load(reference_type, flags, elem_addr, 0);
 
                 let elem_is_null = builder.ins().is_null(elem);
@@ -1551,7 +1738,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
             WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
                 match plan.style {
                     TableStyle::CallerChecksSignature => {
-                        let table_entry_addr = table_data.prepare_table_addr(
+                        let (table_entry_addr, flags) = table_data.prepare_table_addr(
                             builder,
                             index,
                             pointer_type,
@@ -1563,7 +1750,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                         let value_with_init_bit = builder
                             .ins()
                             .bor_imm(value, Imm64::from(FUNCREF_INIT_BIT as i64));
-                        let flags = ir::MemFlags::trusted().with_table();
                         builder
                             .ins()
                             .store(flags, value_with_init_bit, table_entry_addr, 0);
@@ -1627,14 +1813,16 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                 // not recorded in the stack map (which would lead to the GC
                 // saving a reference to a deallocated object, and then using it
                 // after its been freed).
-                let table_entry_addr = table_data.prepare_table_addr(
+                let (table_entry_addr, flags) = table_data.prepare_table_addr(
                     builder,
                     index,
                     pointer_type,
                     self.isa.flags().enable_table_access_spectre_mitigation(),
                 );
-                let flags = ir::MemFlags::trusted().with_table();
                 let current_elem = builder.ins().load(pointer_type, flags, table_entry_addr, 0);
+
+                // After the load, a store to the same address can't trap.
+                let flags = ir::MemFlags::trusted().with_alias_region(Some(ir::AliasRegion::Table));
                 builder.ins().store(flags, value, table_entry_addr, 0);
 
                 // If value is not null, increment `value`'s ref count.
@@ -1930,16 +2118,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                     // VMMemoryDefinition` to it and dereference that when
                     // atomically growing it.
                     let from_offset = self.offsets.vmctx_vmmemory_pointer(def_index);
-                    let memory = func.create_global_value(ir::GlobalValueData::Load {
-                        base: vmctx,
-                        offset: Offset32::new(i32::try_from(from_offset).unwrap()),
-                        global_type: pointer_type,
-                        flags: MemFlags::trusted().with_readonly(),
-                    });
+                    let (memory, def_mt) = self.load_pointer_with_memtypes(
+                        func,
+                        vmctx,
+                        from_offset,
+                        true,
+                        self.pcc_vmctx_memtype,
+                    );
                     let base_offset = i32::from(self.offsets.ptr.vmmemory_definition_base());
                     let current_length_offset =
                         i32::from(self.offsets.ptr.vmmemory_definition_current_length());
-                    (memory, base_offset, current_length_offset, None)
+                    (memory, base_offset, current_length_offset, def_mt)
                 } else {
                     let owned_index = self.module.owned_memory_index(def_index);
                     let owned_base_offset =
@@ -1958,16 +2147,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                 }
             } else {
                 let from_offset = self.offsets.vmctx_vmmemory_import_from(index);
-                let memory = func.create_global_value(ir::GlobalValueData::Load {
-                    base: vmctx,
-                    offset: Offset32::new(i32::try_from(from_offset).unwrap()),
-                    global_type: pointer_type,
-                    flags: MemFlags::trusted().with_readonly(),
-                });
+                let (memory, def_mt) = self.load_pointer_with_memtypes(
+                    func,
+                    vmctx,
+                    from_offset,
+                    true,
+                    self.pcc_vmctx_memtype,
+                );
                 let base_offset = i32::from(self.offsets.ptr.vmmemory_definition_base());
                 let current_length_offset =
                     i32::from(self.offsets.ptr.vmmemory_definition_current_length());
-                (memory, base_offset, current_length_offset, None)
+                (memory, base_offset, current_length_offset, def_mt)
             }
         };
 
@@ -2033,7 +2223,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                                 );
                                 *size = std::cmp::max(*size, fields_end);
                             }
-                            _ => {}
+                            _ => {
+                                panic!("Bad memtype");
+                            }
                         }
                         // Apply a fact to the base pointer.
                         (Some(base_fact), Some(data_mt))
@@ -2092,7 +2284,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                                     offset + u64::from(self.isa.pointer_type().bytes()),
                                 );
                             }
-                            _ => {}
+                            _ => {
+                                panic!("Bad memtype");
+                            }
                         }
                         // Apply a fact to the base pointer.
                         (Some(base_fact), Some(data_mt))
@@ -2228,7 +2422,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
+    ) -> WasmResult<Option<ir::Inst>> {
         Call::new(builder, self).indirect_call(table_index, ty_index, sig_ref, callee, call_args)
     }
 
@@ -2722,16 +2916,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         if self.tunables.consume_fuel && state.reachable() {
             self.fuel_function_exit(builder);
         }
-        if let Some(pcc_vmctx_memtype) = self.pcc_vmctx_memtype {
-            // Sort the fields by offset in the struct definition for
-            // vmctx, now that we've completed it.
-            match &mut builder.func.memory_types[pcc_vmctx_memtype] {
-                ir::MemoryTypeData::Struct { fields, .. } => {
-                    fields.sort_by_key(|f| f.offset);
-                }
-                _ => {}
-            }
-        }
         Ok(())
     }
 
diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs
index 0202631d9..d14382a8b 100644
--- a/crates/environ/src/trap_encoding.rs
+++ b/crates/environ/src/trap_encoding.rs
@@ -98,6 +98,42 @@ pub enum Trap {
     // if adding a variant here be sure to update the `check!` macro below
 }
 
+impl Trap {
+    /// Converts a byte back into a `Trap` if its in-bounds
+    pub fn from_u8(byte: u8) -> Option<Trap> {
+        // FIXME: this could use some sort of derive-like thing to avoid having to
+        // deduplicate the names here.
+        //
+        // This simply converts from the a `u8`, to the `Trap` enum.
+        macro_rules! check {
+            ($($name:ident)*) => ($(if byte == Trap::$name as u8 {
+                return Some(Trap::$name);
+            })*);
+        }
+
+        check! {
+            StackOverflow
+            MemoryOutOfBounds
+            HeapMisaligned
+            TableOutOfBounds
+            IndirectCallToNull
+            BadSignature
+            IntegerOverflow
+            IntegerDivisionByZero
+            BadConversionToInteger
+            UnreachableCodeReached
+            Interrupt
+            AlwaysTrapAdapter
+            OutOfFuel
+            AtomicWaitNonSharedMemory
+            NullReference
+            CannotEnterComponent
+        }
+
+        None
+    }
+}
+
 impl fmt::Display for Trap {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         use Trap::*;
@@ -206,40 +242,9 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
         .binary_search_by_key(&offset, |val| val.get(LittleEndian))
         .ok()?;
     debug_assert!(index < traps.len());
-    let trap = *traps.get(index)?;
+    let byte = *traps.get(index)?;
 
-    // FIXME: this could use some sort of derive-like thing to avoid having to
-    // deduplicate the names here.
-    //
-    // This simply converts from the `trap`, a `u8`, to the `Trap` enum.
-    macro_rules! check {
-        ($($name:ident)*) => ($(if trap == Trap::$name as u8 {
-            return Some(Trap::$name);
-        })*);
-    }
-
-    check! {
-        StackOverflow
-        MemoryOutOfBounds
-        HeapMisaligned
-        TableOutOfBounds
-        IndirectCallToNull
-        BadSignature
-        IntegerOverflow
-        IntegerDivisionByZero
-        BadConversionToInteger
-        UnreachableCodeReached
-        Interrupt
-        AlwaysTrapAdapter
-        OutOfFuel
-        AtomicWaitNonSharedMemory
-        NullReference
-        CannotEnterComponent
-    }
-
-    if cfg!(debug_assertions) {
-        panic!("missing mapping for {}", trap);
-    } else {
-        None
-    }
+    let trap = Trap::from_u8(byte);
+    debug_assert!(trap.is_some(), "missing mapping for {}", byte);
+    trap
 }
diff --git a/crates/runtime/src/sys/custom/traphandlers.rs b/crates/runtime/src/sys/custom/traphandlers.rs
index 7c2508c50..65ea6afe5 100644
--- a/crates/runtime/src/sys/custom/traphandlers.rs
+++ b/crates/runtime/src/sys/custom/traphandlers.rs
@@ -1,4 +1,4 @@
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::mem;
 
@@ -38,12 +38,16 @@ extern "C" fn handle_trap(ip: usize, fp: usize, has_faulting_addr: bool, faultin
             None
         };
         let ip = ip as *const u8;
-        let jmp_buf = info.take_jmp_buf_if_trap(ip, |_handler| {
+        let test = info.test_if_trap(ip, |_handler| {
             panic!("custom signal handlers are not supported on this platform");
         });
-        if !jmp_buf.is_null() {
-            info.set_jit_trap(ip, fp, faulting_addr);
-            unsafe { wasmtime_longjmp(jmp_buf) }
+        match test {
+            TrapTest::NotWasm => {}
+            TrapTest::HandledByEmbedder => unreachable!(),
+            TrapTest::Trap { jmp_buf, trap } => {
+                info.set_jit_trap(ip, fp, faulting_addr, trap);
+                unsafe { wasmtime_longjmp(jmp_buf) }
+            }
         }
     })
 }
diff --git a/crates/runtime/src/sys/unix/machports.rs b/crates/runtime/src/sys/unix/machports.rs
index 3b6f8dfa5..83a4c5037 100644
--- a/crates/runtime/src/sys/unix/machports.rs
+++ b/crates/runtime/src/sys/unix/machports.rs
@@ -49,6 +49,7 @@ use mach2::traps::*;
 use std::mem;
 use std::ptr::addr_of_mut;
 use std::thread;
+use wasmtime_environ::Trap;
 
 /// Process-global port that we use to route thread-level exceptions to.
 static mut WASMTIME_PORT: mach_port_name_t = MACH_PORT_NULL;
@@ -195,10 +196,10 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
     // the second `code` field. If we're ever interested in it the first code
     // field has a `kern_return_t` describing the kind of failure (e.g. SIGSEGV
     // vs SIGBUS), but we're not interested in that right now.
-    let (fault1, fault2) = if request.body.exception as u32 == EXC_BAD_ACCESS {
-        (1, request.body.code[1] as usize)
+    let faulting_addr = if request.body.exception as u32 == EXC_BAD_ACCESS {
+        Some(request.body.code[1] as usize)
     } else {
-        (0, 0)
+        None
     };
 
     // Depending on the current architecture various bits and pieces of this
@@ -232,7 +233,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rbp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
                 // The x86_64 ABI requires a 16-byte stack alignment for
                 // functions, so typically we'll be 16-byte aligned. In this
                 // case we simulate a `call` instruction by decrementing the
@@ -261,6 +262,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rsi = fp as u64;
                 state.__rdx = fault1 as u64;
                 state.__rcx = fault2 as u64;
+                state.__r8 = trap as u64;
             };
             let mut thread_state = ThreadState::new();
         } else if #[cfg(target_arch = "aarch64")] {
@@ -273,7 +275,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__fp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
                 // Clobber LR with the faulting PC, so unwinding resumes at the
                 // faulting instruction. The previous value of LR has been saved
                 // by the callee (in Cranelift generated code), so no need to
@@ -286,6 +288,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__x[1] = fp as u64;
                 state.__x[2] = fault1 as u64;
                 state.__x[3] = fault2 as u64;
+                state.__x[4] = trap as u64;
                 state.__pc = unwind as u64;
             };
             let mut thread_state = mem::zeroed::<ThreadState>();
@@ -322,15 +325,20 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
     // pointer value and if `MAP` changes happen after we read our entry that's
     // ok since they won't invalidate our entry.
     let (pc, fp) = get_pc_and_fp(&thread_state);
-    if !crate::traphandlers::IS_WASM_PC(pc as usize) {
-        return false;
-    }
+    let trap = match crate::traphandlers::GET_WASM_TRAP(pc as usize) {
+        Some(trap) => trap,
+        None => return false,
+    };
 
     // We have determined that this is a wasm trap and we need to actually
     // force the thread itself to trap. The thread's register state is
     // configured to resume in the `unwind` function below, we update the
     // thread's register state, and then we're off to the races.
-    resume(&mut thread_state, pc as usize, fp, fault1, fault2);
+    let (fault1, fault2) = match faulting_addr {
+        None => (0, 0),
+        Some(addr) => (1, addr),
+    };
+    resume(&mut thread_state, pc as usize, fp, fault1, fault2, trap);
     let kret = thread_set_state(
         origin_thread,
         thread_state_flavor,
@@ -350,17 +358,18 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
 unsafe extern "C" fn unwind(
     wasm_pc: *const u8,
     wasm_fp: usize,
-    has_faulting_addr: usize,
-    faulting_addr: usize,
+    fault1: usize,
+    fault2: usize,
+    trap: u8,
 ) -> ! {
     let jmp_buf = tls::with(|state| {
         let state = state.unwrap();
-        let faulting_addr = if has_faulting_addr != 0 {
-            Some(faulting_addr)
-        } else {
-            None
+        let faulting_addr = match fault1 {
+            0 => None,
+            _ => Some(fault2),
         };
-        state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr);
+        let trap = Trap::from_u8(trap).unwrap();
+        state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr, trap);
         state.take_jmp_buf()
     });
     debug_assert!(!jmp_buf.is_null());
diff --git a/crates/runtime/src/sys/unix/signals.rs b/crates/runtime/src/sys/unix/signals.rs
index df6f25a40..45961a0ea 100644
--- a/crates/runtime/src/sys/unix/signals.rs
+++ b/crates/runtime/src/sys/unix/signals.rs
@@ -1,6 +1,6 @@
 //! Trap handling on Unix based on POSIX signals.
 
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::cell::RefCell;
 use std::io;
@@ -111,23 +111,22 @@ unsafe extern "C" fn trap_handler(
         // handling, and reset our trap handling flag. Then we figure
         // out what to do based on the result of the trap handling.
         let (pc, fp) = get_pc_and_fp(context, signum);
-        let jmp_buf = info.take_jmp_buf_if_trap(pc, |handler| handler(signum, siginfo, context));
+        let test = info.test_if_trap(pc, |handler| handler(signum, siginfo, context));
 
         // Figure out what to do based on the result of this handling of
         // the trap. Note that our sentinel value of 1 means that the
         // exception was handled by a custom exception handler, so we
         // keep executing.
-        if jmp_buf.is_null() {
-            return false;
-        }
-        if jmp_buf as usize == 1 {
-            return true;
-        }
+        let (jmp_buf, trap) = match test {
+            TrapTest::NotWasm => return false,
+            TrapTest::HandledByEmbedder => return true,
+            TrapTest::Trap { jmp_buf, trap } => (jmp_buf, trap),
+        };
         let faulting_addr = match signum {
             libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
             _ => None,
         };
-        info.set_jit_trap(pc, fp, faulting_addr);
+        info.set_jit_trap(pc, fp, faulting_addr, trap);
         // On macOS this is a bit special, unfortunately. If we were to
         // `siglongjmp` out of the signal handler that notably does
         // *not* reset the sigaltstack state of our signal handler. This
diff --git a/crates/runtime/src/sys/windows/traphandlers.rs b/crates/runtime/src/sys/windows/traphandlers.rs
index f383477ff..6563d3986 100644
--- a/crates/runtime/src/sys/windows/traphandlers.rs
+++ b/crates/runtime/src/sys/windows/traphandlers.rs
@@ -1,4 +1,4 @@
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::io;
 use windows_sys::Win32::Foundation::*;
@@ -88,14 +88,13 @@ unsafe extern "system" fn exception_handler(exception_info: *mut EXCEPTION_POINT
         } else {
             None
         };
-        let jmp_buf = info.take_jmp_buf_if_trap(ip, |handler| handler(exception_info));
-        if jmp_buf.is_null() {
-            ExceptionContinueSearch
-        } else if jmp_buf as usize == 1 {
-            ExceptionContinueExecution
-        } else {
-            info.set_jit_trap(ip, fp, faulting_addr);
-            wasmtime_longjmp(jmp_buf)
+        match info.test_if_trap(ip, |handler| handler(exception_info)) {
+            TrapTest::NotWasm => ExceptionContinueSearch,
+            TrapTest::HandledByEmbedder => ExceptionContinueExecution,
+            TrapTest::Trap { jmp_buf, trap } => {
+                info.set_jit_trap(ip, fp, faulting_addr, trap);
+                wasmtime_longjmp(jmp_buf)
+            }
         }
     })
 }
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 88cbf5609..92003e8b3 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -24,7 +24,7 @@ pub use traphandlers::SignalHandler;
 ///
 /// This is initialized during `init_traps` below. The definition lives within
 /// `wasmtime` currently.
-pub(crate) static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
+pub(crate) static mut GET_WASM_TRAP: fn(usize) -> Option<wasmtime_environ::Trap> = |_| None;
 
 /// This function is required to be called before any WebAssembly is entered.
 /// This will configure global state such as signal handlers to prepare the
@@ -39,11 +39,14 @@ pub(crate) static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
 /// program counter is the pc of an actual wasm trap or not. This is then used
 /// to disambiguate faults that happen due to wasm and faults that happen due to
 /// bugs in Rust or elsewhere.
-pub fn init_traps(is_wasm_pc: fn(usize) -> bool, macos_use_mach_ports: bool) {
+pub fn init_traps(
+    get_wasm_trap: fn(usize) -> Option<wasmtime_environ::Trap>,
+    macos_use_mach_ports: bool,
+) {
     static INIT: Once = Once::new();
 
     INIT.call_once(|| unsafe {
-        IS_WASM_PC = is_wasm_pc;
+        GET_WASM_TRAP = get_wasm_trap;
         traphandlers::platform_init(macos_use_mach_ports);
     });
 
@@ -157,6 +160,9 @@ pub enum TrapReason {
         /// fault-based traps which are one of the main ways, but not the only
         /// way, to run wasm.
         faulting_addr: Option<usize>,
+
+        /// The trap code associated with this trap.
+        trap: wasmtime_environ::Trap,
     },
 
     /// A trap raised from a wasm libcall
@@ -198,6 +204,22 @@ impl From<wasmtime_environ::Trap> for TrapReason {
     }
 }
 
+/// Return value from `test_if_trap`.
+pub(crate) enum TrapTest {
+    /// Not a wasm trap, need to delegate to whatever process handler is next.
+    NotWasm,
+    /// This trap was handled by the embedder via custom embedding APIs.
+    HandledByEmbedder,
+    /// This is a wasm trap, it needs to be handled.
+    #[cfg_attr(miri, allow(dead_code))]
+    Trap {
+        /// How to longjmp back to the original wasm frame.
+        jmp_buf: *const u8,
+        /// The trap code of this trap.
+        trap: wasmtime_environ::Trap,
+    },
+}
+
 /// Catches any wasm traps that happen within the execution of `closure`,
 /// returning them as a `Result`.
 ///
@@ -406,14 +428,14 @@ impl CallThreadState {
     /// * a different pointer - a jmp_buf buffer to longjmp to, meaning that
     ///   the wasm trap was succesfully handled.
     #[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
-    pub(crate) fn take_jmp_buf_if_trap(
+    pub(crate) fn test_if_trap(
         &self,
         pc: *const u8,
         call_handler: impl Fn(&SignalHandler) -> bool,
-    ) -> *const u8 {
+    ) -> TrapTest {
         // If we haven't even started to handle traps yet, bail out.
         if self.jmp_buf.get().is_null() {
-            return ptr::null();
+            return TrapTest::NotWasm;
         }
 
         // First up see if any instance registered has a custom trap handler,
@@ -421,18 +443,22 @@ impl CallThreadState {
         // return that the trap was handled.
         if let Some(handler) = self.signal_handler {
             if unsafe { call_handler(&*handler) } {
-                return 1 as *const _;
+                return TrapTest::HandledByEmbedder;
             }
         }
 
         // If this fault wasn't in wasm code, then it's not our problem
-        if unsafe { !IS_WASM_PC(pc as usize) } {
-            return ptr::null();
-        }
+        let trap = match unsafe { GET_WASM_TRAP(pc as usize) } {
+            Some(trap) => trap,
+            None => return TrapTest::NotWasm,
+        };
 
         // If all that passed then this is indeed a wasm trap, so return the
         // `jmp_buf` passed to `wasmtime_longjmp` to resume.
-        self.take_jmp_buf()
+        TrapTest::Trap {
+            jmp_buf: self.take_jmp_buf(),
+            trap,
+        }
     }
 
     pub(crate) fn take_jmp_buf(&self) -> *const u8 {
@@ -440,7 +466,13 @@ impl CallThreadState {
     }
 
     #[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
-    pub(crate) fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option<usize>) {
+    pub(crate) fn set_jit_trap(
+        &self,
+        pc: *const u8,
+        fp: usize,
+        faulting_addr: Option<usize>,
+        trap: wasmtime_environ::Trap,
+    ) {
         let backtrace = self.capture_backtrace(self.limits, Some((pc as usize, fp)));
         let coredump = self.capture_coredump(self.limits, Some((pc as usize, fp)));
         unsafe {
@@ -448,6 +480,7 @@ impl CallThreadState {
                 UnwindReason::Trap(TrapReason::Jit {
                     pc: pc as usize,
                     faulting_addr,
+                    trap,
                 }),
                 backtrace,
                 coredump,
@@ -488,15 +521,6 @@ impl CallThreadState {
     }
 }
 
-struct ResetCell<'a, T: Copy>(&'a Cell<T>, T);
-
-impl<T: Copy> Drop for ResetCell<'_, T> {
-    #[inline]
-    fn drop(&mut self) {
-        self.0.set(self.1);
-    }
-}
-
 // A private inner module for managing the TLS state that we require across
 // calls in wasm. The WebAssembly code is called from C++ and then a trap may
 // happen which requires us to read some contextual state to figure out what to
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index bef3e7061..271c9bc25 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -1009,9 +1009,14 @@ impl Config {
     /// Configures whether Cranelift should perform a NaN-canonicalization pass.
     ///
     /// When Cranelift is used as a code generation backend this will configure
-    /// it to replace NaNs with a single canonical value. This is useful for users
-    /// requiring entirely deterministic WebAssembly computation.
-    /// This is not required by the WebAssembly spec, so it is not enabled by default.
+    /// it to replace NaNs with a single canonical value. This is useful for
+    /// users requiring entirely deterministic WebAssembly computation.  This is
+    /// not required by the WebAssembly spec, so it is not enabled by default.
+    ///
+    /// Note that this option affects not only WebAssembly's `f32` and `f64`
+    /// types but additionally the `v128` type. This option will cause
+    /// operations using any of these types to have extra checks placed after
+    /// them to normalize NaN values as needed.
     ///
     /// The default value for this is `false`
     #[cfg(any(feature = "cranelift", feature = "winch"))]
diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs
index b6ec980aa..aa69554a8 100644
--- a/crates/wasmtime/src/engine.rs
+++ b/crates/wasmtime/src/engine.rs
@@ -86,10 +86,7 @@ impl Engine {
             // Ensure that wasmtime_runtime's signal handlers are configured. This
             // is the per-program initialization required for handling traps, such
             // as configuring signals, vectored exception handlers, etc.
-            wasmtime_runtime::init_traps(
-                crate::module::is_wasm_trap_pc,
-                config.macos_use_mach_ports,
-            );
+            wasmtime_runtime::init_traps(crate::module::get_wasm_trap, config.macos_use_mach_ports);
             #[cfg(feature = "debug-builtins")]
             wasmtime_runtime::debug_builtins::ensure_exported();
         }
diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs
index ca59e7070..3cc8aae5b 100644
--- a/crates/wasmtime/src/runtime/module.rs
+++ b/crates/wasmtime/src/runtime/module.rs
@@ -28,7 +28,7 @@ use wasmtime_runtime::{
 mod registry;
 
 pub use registry::{
-    is_wasm_trap_pc, register_code, unregister_code, ModuleRegistry, RegisteredModuleId,
+    get_wasm_trap, register_code, unregister_code, ModuleRegistry, RegisteredModuleId,
 };
 
 /// A compiled WebAssembly module, ready to be instantiated.
diff --git a/crates/wasmtime/src/runtime/module/registry.rs b/crates/wasmtime/src/runtime/module/registry.rs
index 9c8b50528..2c8e7081b 100644
--- a/crates/wasmtime/src/runtime/module/registry.rs
+++ b/crates/wasmtime/src/runtime/module/registry.rs
@@ -34,9 +34,9 @@ pub struct ModuleRegistry {
 }
 
 struct LoadedCode {
-    /// Representation of loaded code which could be either a component or a
-    /// module.
-    code: Arc<CodeObject>,
+    /// Kept alive here in the store to have a strong reference to keep the
+    /// relevant code mapped while the store is alive.
+    _code: Arc<CodeObject>,
 
     /// Modules found within `self.code`, keyed by start address here of the
     /// address of the first function in the module.
@@ -153,7 +153,7 @@ impl ModuleRegistry {
         }
 
         let mut item = LoadedCode {
-            code: code.clone(),
+            _code: code.clone(),
             modules: Default::default(),
         };
         if let Some(module) = module {
@@ -164,12 +164,6 @@ impl ModuleRegistry {
         id
     }
 
-    /// Fetches trap information about a program counter in a backtrace.
-    pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
-        let (code, offset) = self.code(pc)?;
-        wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
-    }
-
     /// Fetches frame information about a program counter in a backtrace.
     ///
     /// Returns an object if this `pc` is known to some previously registered
@@ -260,21 +254,21 @@ type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
 
 /// Returns whether the `pc`, according to globally registered information,
 /// is a wasm trap or not.
-pub fn is_wasm_trap_pc(pc: usize) -> bool {
+pub fn get_wasm_trap(pc: usize) -> Option<Trap> {
     let (code, text_offset) = {
         let all_modules = GLOBAL_CODE.read().unwrap();
 
         let (end, (start, module)) = match all_modules.range(pc..).next() {
             Some(info) => info,
-            None => return false,
+            None => return None,
         };
         if pc < *start || *end < pc {
-            return false;
+            return None;
         }
         (module.clone(), pc - *start)
     };
 
-    wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset).is_some()
+    wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset)
 }
 
 /// Registers a new region of code.
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index fa3966264..a78353568 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -1595,10 +1595,26 @@ impl StoreOpaque {
     /// always zero in these situations which means that the trapping context
     /// doesn't have enough information to report the fault address.
     pub(crate) fn wasm_fault(&self, pc: usize, addr: usize) -> Option<WasmFault> {
-        // Explicitly bounds-checked memories with spectre-guards enabled will
-        // cause out-of-bounds accesses to get routed to address 0, so allow
-        // wasm instructions to fault on the null address.
-        if addr == 0 {
+        // There are a few instances where a "close to zero" pointer is loaded
+        // and we expect that to happen:
+        //
+        // * Explicitly bounds-checked memories with spectre-guards enabled will
+        //   cause out-of-bounds accesses to get routed to address 0, so allow
+        //   wasm instructions to fault on the null address.
+        // * `call_indirect` when invoking a null function pointer may load data
+        //   from the a `VMFuncRef` whose address is null, meaning any field of
+        //   `VMFuncRef` could be the address of the fault.
+        //
+        // In these situations where the address is so small it won't be in any
+        // instance, so skip the checks below.
+        if addr <= mem::size_of::<VMFuncRef>() {
+            const _: () = {
+                // static-assert that `VMFuncRef` isn't too big to ensure that
+                // it lives solely within the first page as we currently only
+                // have the guarantee that the first page of memory is unmapped,
+                // no more.
+                assert!(mem::size_of::<VMFuncRef>() <= 512);
+            };
             return None;
         }
 
diff --git a/crates/wasmtime/src/runtime/trap.rs b/crates/wasmtime/src/runtime/trap.rs
index ba4bcaeff..8d5b52188 100644
--- a/crates/wasmtime/src/runtime/trap.rs
+++ b/crates/wasmtime/src/runtime/trap.rs
@@ -109,12 +109,12 @@ pub(crate) fn from_runtime_box(
             );
             (error, None)
         }
-        wasmtime_runtime::TrapReason::Jit { pc, faulting_addr } => {
-            let code = store
-                .modules()
-                .lookup_trap_code(pc)
-                .unwrap_or(Trap::StackOverflow);
-            let mut err: Error = code.into();
+        wasmtime_runtime::TrapReason::Jit {
+            pc,
+            faulting_addr,
+            trap,
+        } => {
+            let mut err: Error = trap.into();
 
             // If a fault address was present, for example with segfaults,
             // then simultaneously assert that it's within a known linear memory
diff --git a/examples/async.cpp b/examples/async.cpp
index a5feae115..fac006a2a 100644
--- a/examples/async.cpp
+++ b/examples/async.cpp
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    c++ examples/async.cpp \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -std=c++11 \
        -lpthread -ldl -lm \
diff --git a/examples/fuel.c b/examples/fuel.c
index 945dc5bf5..521d05628 100644
--- a/examples/fuel.c
+++ b/examples/fuel.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/fuel.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o fuel
diff --git a/examples/gcd.c b/examples/gcd.c
index 9f7f45c0a..7447f8f4a 100644
--- a/examples/gcd.c
+++ b/examples/gcd.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/gcd.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o gcd
diff --git a/examples/hello.c b/examples/hello.c
index dc7343432..45ff62072 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/hello.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o hello
diff --git a/examples/interrupt.c b/examples/interrupt.c
index 13292f2ca..3866cec1a 100644
--- a/examples/interrupt.c
+++ b/examples/interrupt.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/interrupt.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o interrupt
diff --git a/examples/linking.c b/examples/linking.c
index d048451c2..d9741ab97 100644
--- a/examples/linking.c
+++ b/examples/linking.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/linking.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o linking
diff --git a/examples/memory.c b/examples/memory.c
index 3f4bf311e..2edca2198 100644
--- a/examples/memory.c
+++ b/examples/memory.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/memory.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o memory
diff --git a/examples/multi.c b/examples/multi.c
index 6edcdd18b..af1e61157 100644
--- a/examples/multi.c
+++ b/examples/multi.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/multi.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o multi
diff --git a/examples/multimemory.c b/examples/multimemory.c
index 88f1f5752..8fa4973e7 100644
--- a/examples/multimemory.c
+++ b/examples/multimemory.c
@@ -6,7 +6,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/multimemory.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o multimemory
diff --git a/examples/serialize.c b/examples/serialize.c
index 1d3acc7b3..65fb7263a 100644
--- a/examples/serialize.c
+++ b/examples/serialize.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/hello.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o hello
diff --git a/examples/threads.c b/examples/threads.c
index 7e13b9f3a..d89480db6 100644
--- a/examples/threads.c
+++ b/examples/threads.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/threads.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o threads
diff --git a/examples/wasi/main.c b/examples/wasi/main.c
index 753d66497..520259d53 100644
--- a/examples/wasi/main.c
+++ b/examples/wasi/main.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/wasi/main.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o wasi
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 8f25ba91e..1413874ee 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -607,7 +607,22 @@ impl RunCommand {
         store: &mut Store<Host>,
         module: &RunTarget,
     ) -> Result<()> {
-        if self.run.common.wasi.common != Some(false) {
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        if cli != Some(false) {
             match linker {
                 CliLinker::Core(linker) => {
                     match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
@@ -740,6 +755,11 @@ impl RunCommand {
         let mut builder = WasiCtxBuilder::new();
         builder.inherit_stdio().args(&self.compute_argv()?)?;
 
+        if self.run.common.wasi.inherit_env == Some(true) {
+            for (k, v) in std::env::vars() {
+                builder.env(&k, &v)?;
+            }
+        }
         for (key, value) in self.vars.iter() {
             let value = match value {
                 Some(value) => value.clone(),
@@ -779,6 +799,11 @@ impl RunCommand {
         let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
         builder.inherit_stdio().args(&self.compute_argv()?);
 
+        if self.run.common.wasi.inherit_env == Some(true) {
+            for (k, v) in std::env::vars() {
+                builder.env(&k, &v);
+            }
+        }
         for (key, value) in self.vars.iter() {
             let value = match value {
                 Some(value) => value.clone(),
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index e59f91839..c35182b59 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -191,16 +191,30 @@ impl ServeCommand {
     }
 
     fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
-        // Repurpose the `-Scommon` flag of `wasmtime run` for `wasmtime serve`
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        // Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
         // to serve as a signal to enable all WASI interfaces instead of just
-        // those in the `proxy` world. If `-Scommon` is present then add all
-        // `command` APIs which includes all "common" or base WASI APIs and then
-        // additionally add in the required HTTP APIs.
+        // those in the `proxy` world. If `-Scli` is present then add all
+        // `command` APIs and then additionally add in the required HTTP APIs.
         //
-        // If `-Scommon` isn't passed then use the `proxy::add_to_linker`
+        // If `-Scli` isn't passed then use the `proxy::add_to_linker`
         // bindings which adds just those interfaces that the proxy interface
         // uses.
-        if self.run.common.wasi.common == Some(true) {
+        if cli == Some(true) {
             wasmtime_wasi::command::add_to_linker(linker)?;
             wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?;
         } else {
diff --git a/src/old_cli.rs b/src/old_cli.rs
index f6bbe5ff4..c863fc1fd 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -981,7 +981,7 @@ impl CommonOptions {
             ret.wasi.http = wasi_http;
             ret.wasi.nn = wasi_nn;
             ret.wasi.threads = wasi_threads;
-            ret.wasi.common = wasi_common;
+            ret.wasi.cli = wasi_common;
         }
         ret
     }
diff --git a/winch/codegen/src/codegen/bounds.rs b/winch/codegen/src/codegen/bounds.rs
index c44ce172f..c1fb6d395 100644
--- a/winch/codegen/src/codegen/bounds.rs
+++ b/winch/codegen/src/codegen/bounds.rs
@@ -125,7 +125,7 @@ pub(crate) fn ensure_index_and_offset<M: MacroAssembler>(
     masm: &mut M,
     index: Index,
     offset: u64,
-    ptr_size: OperandSize,
+    heap_ty_size: OperandSize,
 ) -> ImmOffset {
     match u32::try_from(offset) {
         // If the immediate offset fits in a u32, then we simply return.
@@ -137,7 +137,7 @@ pub(crate) fn ensure_index_and_offset<M: MacroAssembler>(
                 index.as_typed_reg().into(),
                 index.as_typed_reg().into(),
                 RegImm::i64(offset as i64),
-                ptr_size,
+                heap_ty_size,
                 TrapCode::HeapOutOfBounds,
             );
 
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 8358d71d8..0288606cc 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -492,7 +492,8 @@ where
         let memory_index = MemoryIndex::from_u32(memarg.memory);
         let heap = self.env.resolve_heap(memory_index);
         let index = Index::from_typed_reg(self.context.pop_to_reg(self.masm, None));
-        let offset = bounds::ensure_index_and_offset(self.masm, index, memarg.offset, ptr_size);
+        let offset =
+            bounds::ensure_index_and_offset(self.masm, index, memarg.offset, heap.ty.into());
         let offset_with_access_size = add_offset_and_access_size(offset, access_size);
 
         let addr = match heap.style {
@@ -528,7 +529,7 @@ where
                     index_offset_and_access_size,
                     index_offset_and_access_size,
                     RegImm::i64(offset_with_access_size as i64),
-                    ptr_size,
+                    heap.ty.into(),
                     TrapCode::HeapOutOfBounds,
                 );
 
@@ -627,7 +628,11 @@ where
                     |masm, bounds, index| {
                         let adjusted_bounds = bounds.as_u64() - offset_with_access_size;
                         let index_reg = index.as_typed_reg().reg;
-                        masm.cmp(RegImm::i64(adjusted_bounds as i64), index_reg, ptr_size);
+                        masm.cmp(
+                            RegImm::i64(adjusted_bounds as i64),
+                            index_reg,
+                            heap.ty.into(),
+                        );
                         IntCmpKind::GtU
                     },
                 );
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 3ad1ec585..da9051302 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -10,6 +10,7 @@ use crate::masm::{
     DivKind, ExtendKind, FloatCmpKind, IntCmpKind, MacroAssembler, MemMoveDirection, OperandSize,
     RegImm, RemKind, RoundingMode, SPOffset, ShiftKind, TruncKind,
 };
+use crate::reg::Reg;
 use crate::stack::{TypedReg, Val};
 use cranelift_codegen::ir::TrapCode;
 use regalloc2::RegClass;
@@ -1593,8 +1594,24 @@ where
         //   [ vmctx, delta, index ]
         self.context.stack.extend([mem.try_into().unwrap()]);
 
+        let heap = self.env.resolve_heap(MemoryIndex::from_u32(mem));
         let builtin = self.env.builtins.memory32_grow::<M::ABI, M::Ptr>();
-        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin));
+
+        // The memory32_grow builtin returns a pointer type, therefore we must
+        // ensure that the return type is representative of the address space of
+        // the heap type.
+        match (self.env.ptr_type(), heap.ty) {
+            (WasmValType::I64, WasmValType::I64) => {}
+            // When the heap type is smaller than the pointer type, we adjust
+            // the result of the memory32_grow builtin.
+            (WasmValType::I64, WasmValType::I32) => {
+                let top: Reg = self.context.pop_to_reg(self.masm, None).into();
+                self.masm.wrap(top.into(), top.into());
+                self.context.stack.push(TypedReg::i32(top).into());
+            }
+            _ => unimplemented!("Support for 32-bit platforms"),
+        }
     }
 
     fn visit_data_drop(&mut self, data_index: u32) {


Test patch:
diff --git a/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif b/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
index 7b1368a9b..5f137321d 100644
--- a/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
+++ b/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
@@ -62,8 +62,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   vmovsd  0(%rdi), %xmm3
-;   vmovsd  %xmm0, %xmm3, %xmm0
+;   vmovsd  0(%rdi), %xmm4
+;   vmovsd  %xmm0, %xmm4, %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -73,8 +73,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   vmovsd (%rdi), %xmm3 ; trap: heap_oob
-;   vmovsd %xmm3, %xmm0, %xmm0
+;   vmovsd (%rdi), %xmm4 ; trap: heap_oob
+;   vmovsd %xmm4, %xmm0, %xmm0
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -90,8 +90,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzbq  0(%rdi), %rdx
-;   vpinsrb $1, %xmm0, %rdx, %xmm0
+;   vpinsrb $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -101,8 +100,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzbq (%rdi), %rdx ; trap: heap_oob
-;   vpinsrb $1, %edx, %xmm0, %xmm0
+;   vpinsrb $1, (%rdi), %xmm0, %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -118,8 +116,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzwq  0(%rdi), %rdx
-;   vpinsrw $1, %xmm0, %rdx, %xmm0
+;   vpinsrw $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -129,8 +126,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzwq (%rdi), %rdx ; trap: heap_oob
-;   vpinsrw $1, %edx, %xmm0, %xmm0
+;   vpinsrw $1, (%rdi), %xmm0, %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
diff --git a/cranelift/filetests/filetests/isa/x64/insertlane.clif b/cranelift/filetests/filetests/isa/x64/insertlane.clif
index d15afb787..d27740f58 100644
--- a/cranelift/filetests/filetests/isa/x64/insertlane.clif
+++ b/cranelift/filetests/filetests/isa/x64/insertlane.clif
@@ -62,8 +62,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movsd   0(%rdi), %xmm3
-;   movsd   %xmm0, %xmm3, %xmm0
+;   movsd   0(%rdi), %xmm4
+;   movsd   %xmm0, %xmm4, %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -73,8 +73,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movsd (%rdi), %xmm3 ; trap: heap_oob
-;   movsd %xmm3, %xmm0
+;   movsd (%rdi), %xmm4 ; trap: heap_oob
+;   movsd %xmm4, %xmm0
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -90,8 +90,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzbq  0(%rdi), %rdx
-;   pinsrb  $1, %xmm0, %rdx, %xmm0
+;   pinsrb  $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -101,8 +100,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzbq (%rdi), %rdx ; trap: heap_oob
-;   pinsrb $1, %edx, %xmm0
+;   pinsrb $1, (%rdi), %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -118,8 +116,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzwq  0(%rdi), %rdx
-;   pinsrw  $1, %xmm0, %rdx, %xmm0
+;   pinsrw  $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -129,8 +126,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzwq (%rdi), %rdx ; trap: heap_oob
-;   pinsrw $1, %edx, %xmm0
+;   pinsrw $1, (%rdi), %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
diff --git a/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif b/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
index 3331c7765..5a84fa914 100644
--- a/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
+++ b/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
@@ -340,3 +340,135 @@ block0(v0: f32):
 ;   popq %rbp
 ;   retq
 
+
+
+function %load32_lane_coalesced(i64, i32x4) -> i32x4 {
+block0(v0: i64, v1: i32x4):
+    v2 = load.i32 v0
+    v3 = insertlane.i32x4 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrd  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrd $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %load16_lane_coalesced(i64, i16x8) -> i16x8 {
+block0(v0: i64, v1: i16x8):
+    v2 = load.i16 v0
+    v3 = insertlane.i16x8 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrw  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrw $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %load8_lane_coalesced(i64, i8x16) -> i8x16 {
+block0(v0: i64, v1: i8x16):
+    v2 = load.i8 v0
+    v3 = insertlane.i8x16 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrb  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrb $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %store32_lane_coalesced(i64, i32x4) {
+block0(v0: i64, v1: i32x4):
+    v2 = extractlane.i32x4 v1, 3
+    store.i32 v2, v0
+    return
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrd  $3, %xmm0, 0(%rdi)
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrd $3, %xmm0, (%rdi) ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %store8_lane_coalesced(i64, i16x8) {
+block0(v0: i64, v1: i16x8):
+    v2 = extractlane.i16x8 v1, 3
+    store.i16 v2, v0
+    return
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrw  $3, %xmm0, 0(%rdi)
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrw $3, %xmm0, (%rdi) ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/cranelift/filetests/filetests/pcc/succeed/fuzz-float-loads.clif b/cranelift/filetests/filetests/pcc/succeed/fuzz-float-loads.clif
new file mode 100644
index 000000000..af5aa34c8
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/fuzz-float-loads.clif
@@ -0,0 +1,86 @@
+test compile
+set enable_pcc=true
+set opt_level=speed
+target x86_64
+
+function u0:0(i64 vmctx, i64) fast {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned readonly gv0+8
+    gv2 = load.i64 notrap aligned gv1
+    gv3 ! mem(mt0, 0x0, 0x0) = vmctx
+    gv4 ! mem(mt1, 0x0, 0x0) = load.i64 notrap aligned readonly checked gv3+80
+    mt0 = struct 88 { 80: i64 readonly ! mem(mt1, 0x0, 0x0) }
+    mt1 = memory 0x180000000
+    sig0 = (i64 vmctx) system_v
+    sig1 = (i64 vmctx, i32 uext) system_v
+    sig2 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v
+    const0 = 0x02030077ff00ff04030077ff6cff5503
+    stack_limit = gv2
+
+block0(v0: i64, v1: i64):
+    v2 = iconst.i32 0
+    v25 -> v2
+    v45 -> v2
+    v24 -> v25
+    v3 = null.r64
+    v4 = global_value.i64 gv3
+    v5 = load.i64 notrap aligned v4+8
+    v17 -> v5
+    v44 -> v5
+    v42 -> v17
+    v6 = load.i64 notrap aligned v5+8
+    v7 = iadd_imm v6, 1
+    v8 = iconst.i64 0
+    v9 = icmp sge v7, v8  ; v8 = 0
+    brif v9, block2, block3(v7)
+
+block2:
+    store.i64 notrap aligned v7, v5+8
+    v10 = global_value.i64 gv3
+    v11 = load.i64 notrap aligned readonly v10+56
+    v12 = load.i64 notrap aligned readonly v11+120
+    call_indirect sig0, v12(v10)
+    v13 = load.i64 notrap aligned v5+8
+    jump block3(v13)
+
+block3(v43: i64):
+    v15 -> v43
+    jump block4
+
+block4:
+    v14 = iconst.i64 0
+    v16 = icmp.i64 sge v15, v14  ; v14 = 0
+    brif v16, block6, block7(v15)
+
+block6:
+    store.i64 notrap aligned v15, v17+8
+    v18 = global_value.i64 gv3
+    v19 = load.i64 notrap aligned readonly v18+56
+    v20 = load.i64 notrap aligned readonly v19+120
+    call_indirect sig0, v20(v18)
+    v21 = load.i64 notrap aligned v17+8
+    jump block7(v21)
+
+block7(v40: i64):
+    v22 = vconst.i8x16 const0
+    v23 = uwiden_high v22  ; v22 = const0
+    v26 = bitcast.i8x16 little v23
+    v27 = sshr v26, v25  ; v25 = 0
+    v28 = bitcast.i64x2 little v27
+    v29 = extractlane v28, 1
+    v30 = iconst.i32 1
+    v31 = global_value.i64 gv3
+    v32 = load.i64 notrap aligned readonly v31+56
+    v33 = load.i64 notrap aligned readonly v32+24
+    call_indirect sig1, v33(v31, v30)  ; v30 = 1
+    v34 = ireduce.i32 v29
+    v35 ! range(64, 0x0, 0xffffffff) = uextend.i64 v34
+    v36 ! mem(mt1, 0x0, 0x0) = global_value.i64 gv4
+    v37 ! mem(mt1, 0x0, 0xffffffff) = iadd v36, v35
+    v38 = load.f64 little checked heap v37
+    v39 = f64const 0x0.0000000005b28p-1022
+    v41 = iadd_imm v40, 9
+    store notrap aligned v41, v17+8
+    trap unreachable
+}
+
diff --git a/cranelift/filetests/filetests/pcc/succeed/imul-fuzzbug.clif b/cranelift/filetests/filetests/pcc/succeed/imul-fuzzbug.clif
new file mode 100644
index 000000000..8ce327402
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/imul-fuzzbug.clif
@@ -0,0 +1,282 @@
+test compile
+set enable_pcc=true
+set opt_level=speed
+target x86_64
+
+function u0:3(i64 vmctx, i64) fast {
+        gv0 = vmctx
+        gv1 = load.i64 notrap aligned readonly gv0+8
+        gv2 = load.i64 notrap aligned gv1
+        gv3 ! mem(mt0, 0x0, 0x0) = vmctx
+        mt0 = struct 0 { }
+        sig0 = (i64 vmctx, i32 uext) system_v
+        sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+        sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v
+        stack_limit = gv2
+    
+block0(v0 ! mem(mt0, 0x0, 0x0): i64, v1: i64):
+    v13 -> v0
+    v17 -> v0
+    v21 -> v0
+    v22 -> v0
+    v43 -> v0
+    v51 -> v0
+    v155 -> v0
+    v160 -> v0
+    v163 -> v0
+    v164 -> v0
+    v167 -> v0
+    v168 -> v0
+    v171 -> v0
+    v173 -> v0
+    v176 -> v0
+    v177 -> v0
+    v180 -> v0
+    v182 -> v0
+    v185 -> v0
+    v2 = f64const 0.0
+    v3 = iconst.i64 0
+    v4 = iconst.i64 0
+    v5 = iconst.i64 0
+    v6 = iconst.i64 0
+    v7 = iconst.i64 0
+    v8 = iconst.i64 0
+    v9 = iconst.i64 0
+    v10 = iconst.i64 0
+    v11 = iconst.i64 0
+    v12 = iconst.i64 0
+    v14 = load.i32 notrap aligned table v13+192
+    v186 = iconst.i32 0
+    v15 = icmp eq v14, v186  ; v186 = 0
+    v16 = uextend.i32 v15
+    brif v16, block2, block3
+
+block2:
+    trap unreachable
+
+block3:
+    v18 = load.i32 notrap aligned table v17+192
+    v19 = iconst.i32 1
+    v20 = isub v18, v19  ; v19 = 1
+    store notrap aligned table v20, v21+192
+    v23 = load.i32 notrap aligned table v22+128
+    v24 = f64const -0x1.3090455030609p194
+    v25 = fneg v24  ; v24 = -0x1.3090455030609p194
+    v26 = fneg v25
+    v27 = fneg v26
+    v28 = fneg v27
+    v29 = fneg v28
+    v30 = fneg v29
+    v31 = fneg v30
+    v32 = fneg v31
+    v33 = fneg v32
+    v34 = fneg v33
+    v35 = fneg v34
+    v36 = fneg v35
+    v37 = floor v36
+    v38 = f64const +NaN
+    v39 = fcmp eq v37, v37
+    v40 = uextend.i32 v39
+    v41 = select v40, v37, v38  ; v38 = +NaN
+    v42 = iconst.i32 0x1309_0455
+    v44 = load.i32 notrap aligned table v43+128
+    v45 = rotl v42, v44  ; v42 = 0x1309_0455
+    v46 = fcvt_from_sint.f32 v45
+    v47 = iconst.i64 0x2001_8113_0904_5503
+    v48 = iconst.i64 0x2001_8113_0904_5503
+    v187 = iconst.i64 0
+    v49 = icmp eq v48, v187  ; v48 = 0x2001_8113_0904_5503, v187 = 0
+    v50 = uextend.i32 v49
+    store notrap aligned table v50, v51+128
+    v52 = iconst.i32 0x2001_8113
+    v53 = iconst.i32 0x0904_5503
+    v54 = rotl v52, v53  ; v52 = 0x2001_8113, v53 = 0x0904_5503
+    v55 = fcvt_from_sint.f32 v54
+    v56 = iconst.i64 -1
+    v70 -> v56
+    v71 -> v56
+    v57 = iconst.i32 0xffff_ffff
+    v58 = uextend.i64 v57  ; v57 = 0xffff_ffff
+    v59 = iconst.i64 1
+    v188 = iconst.i64 0
+    v60 = icmp eq v58, v188  ; v188 = 0
+    v61 = uextend.i32 v60
+    v62 = select v61, v59, v58  ; v59 = 1
+    v73 -> v62
+    v63 = iconst.i64 0x8000_0000_0000_0000
+    v64 = icmp ne v56, v63  ; v56 = -1, v63 = 0x8000_0000_0000_0000
+    v65 = uextend.i32 v64
+    brif v65, block5, block6
+
+block6:
+    v66 = iconst.i64 -1
+    v67 = icmp.i64 ne v62, v66  ; v66 = -1
+    v68 = uextend.i32 v67
+    brif v68, block5, block7
+
+block7:
+    v69 = iconst.i64 1
+    jump block4(v69)  ; v69 = 1
+
+block5:
+    jump block4(v62)
+
+block4(v72: i64):
+    v74 = srem.i64 v56, v72  ; v56 = -1
+    v88 -> v74
+    v89 -> v74
+    v75 = iconst.i32 0xffff_ffff
+    v76 = uextend.i64 v75  ; v75 = 0xffff_ffff
+    v77 = iconst.i64 1
+    v189 = iconst.i64 0
+    v78 = icmp eq v76, v189  ; v189 = 0
+    v79 = uextend.i32 v78
+    v80 = select v79, v77, v76  ; v77 = 1
+    v91 -> v80
+    v81 = iconst.i64 0x8000_0000_0000_0000
+    v82 = icmp ne v74, v81  ; v81 = 0x8000_0000_0000_0000
+    v83 = uextend.i32 v82
+    brif v83, block9, block10
+
+block10:
+    v84 = iconst.i64 -1
+    v85 = icmp.i64 ne v80, v84  ; v84 = -1
+    v86 = uextend.i32 v85
+    brif v86, block9, block11
+
+block11:
+    v87 = iconst.i64 1
+    jump block8(v87)  ; v87 = 1
+
+block9:
+    jump block8(v80)
+
+block8(v90: i64):
+    v92 = srem.i64 v74, v90
+    v106 -> v92
+    v107 -> v92
+    v93 = iconst.i32 0xffff_ffff
+    v94 = uextend.i64 v93  ; v93 = 0xffff_ffff
+    v95 = iconst.i64 1
+    v190 = iconst.i64 0
+    v96 = icmp eq v94, v190  ; v190 = 0
+    v97 = uextend.i32 v96
+    v98 = select v97, v95, v94  ; v95 = 1
+    v109 -> v98
+    v99 = iconst.i64 0x8000_0000_0000_0000
+    v100 = icmp ne v92, v99  ; v99 = 0x8000_0000_0000_0000
+    v101 = uextend.i32 v100
+    brif v101, block13, block14
+
+block14:
+    v102 = iconst.i64 -1
+    v103 = icmp.i64 ne v98, v102  ; v102 = -1
+    v104 = uextend.i32 v103
+    brif v104, block13, block15
+
+block15:
+    v105 = iconst.i64 1
+    jump block12(v105)  ; v105 = 1
+
+block13:
+    jump block12(v98)
+
+block12(v108: i64):
+    v110 = srem.i64 v92, v108
+    v124 -> v110
+    v125 -> v110
+    v111 = iconst.i32 0xffff_ffff
+    v112 = uextend.i64 v111  ; v111 = 0xffff_ffff
+    v113 = iconst.i64 1
+    v191 = iconst.i64 0
+    v114 = icmp eq v112, v191  ; v191 = 0
+    v115 = uextend.i32 v114
+    v116 = select v115, v113, v112  ; v113 = 1
+    v127 -> v116
+    v117 = iconst.i64 0x8000_0000_0000_0000
+    v118 = icmp ne v110, v117  ; v117 = 0x8000_0000_0000_0000
+    v119 = uextend.i32 v118
+    brif v119, block17, block18
+
+block18:
+    v120 = iconst.i64 -1
+    v121 = icmp.i64 ne v116, v120  ; v120 = -1
+    v122 = uextend.i32 v121
+    brif v122, block17, block19
+
+block19:
+    v123 = iconst.i64 1
+    jump block16(v123)  ; v123 = 1
+
+block17:
+    jump block16(v116)
+
+block16(v126: i64):
+    v128 = srem.i64 v110, v126
+    v145 -> v128
+    v146 -> v128
+    v129 = iconst.i32 0x0944_0909
+    v130 = popcnt v129  ; v129 = 0x0944_0909
+    v131 = iconst.i32 0x3482_3582
+    v132 = imul v130, v131  ; v131 = 0x3482_3582
+    v133 = uextend.i64 v132
+    v134 = iconst.i64 1
+    v192 = iconst.i64 0
+    v135 = icmp eq v133, v192  ; v192 = 0
+    v136 = uextend.i32 v135
+    v137 = select v136, v134, v133  ; v134 = 1
+    v148 -> v137
+    v138 = iconst.i64 0x8000_0000_0000_0000
+    v139 = icmp ne v128, v138  ; v138 = 0x8000_0000_0000_0000
+    v140 = uextend.i32 v139
+    brif v140, block21, block22
+
+block22:
+    v141 = iconst.i64 -1
+    v142 = icmp.i64 ne v137, v141  ; v141 = -1
+    v143 = uextend.i32 v142
+    brif v143, block21, block23
+
+block23:
+    v144 = iconst.i64 1
+    jump block20(v144)  ; v144 = 1
+
+block21:
+    jump block20(v137)
+
+block20(v147: i64):
+    v149 = srem.i64 v128, v147
+    v150 = iconst.i32 0xb7b7_746e
+    v151 = fcvt_from_uint.f32 v150  ; v150 = 0xb7b7_746e
+    v152 = fcvt_to_sint_sat.i64 v151
+    v153 = icmp eq v149, v152
+    v154 = uextend.i32 v153
+    v156 = load.i32 notrap aligned table v155+128
+    v157 = ctz v156
+    v158 = f32const 0x1.bc0402p-124
+    v159 = bitcast.i32 v158  ; v158 = 0x1.bc0402p-124
+    v161 = load.i32 notrap aligned table v160+144
+    v162 = bxor v159, v161
+    store notrap aligned table v162, v163+144
+    v165 = load.i32 notrap aligned table v164+160
+    v166 = bxor v157, v165
+    store notrap aligned table v166, v167+160
+    v169 = load.i32 notrap aligned table v168+160
+    v170 = bxor v154, v169
+    store notrap aligned table v170, v171+160
+    v172 = bitcast.i32 v55
+    v174 = load.i32 notrap aligned table v173+144
+    v175 = bxor v172, v174
+    store notrap aligned table v175, v176+144
+    v178 = load.i64 notrap aligned table v177+176
+    v179 = bxor.i64 v47, v178  ; v47 = 0x2001_8113_0904_5503
+    store notrap aligned table v179, v180+176
+    v181 = bitcast.i32 v46
+    v183 = load.i32 notrap aligned table v182+144
+    v184 = bxor v181, v183
+    store notrap aligned table v184, v185+144
+    jump block1
+
+block1:
+    return
+}
diff --git a/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif b/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif
new file mode 100644
index 000000000..9448298c8
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif
@@ -0,0 +1,119 @@
+test compile
+set enable_pcc=true
+set opt_level=speed
+target x86_64
+
+;; v110 is a uextend'd iconst; v111 is a memtype base; v112 is that
+;; base plus that fixed offset, and should be able to verify that its
+;; offset is in 0..4GiB. In the fuzzbug that inspired this test, we
+;; had a bare `iconst` with no range fact on it after optimization.
+
+function u0:2(i64 vmctx, i64, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32) -> i32, i32, i32, i32, i32, i32 fast {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned readonly gv0+8
+    gv2 = load.i64 notrap aligned gv1
+    gv3 ! mem(mt0, 0x0, 0x0) = vmctx
+    gv4 ! mem(mt1, 0x0, 0x0) = load.i64 notrap aligned readonly checked gv3+96
+    mt0 = struct 104 { 96: i64 readonly ! mem(mt1, 0x0, 0x0) }
+    mt1 = memory 0x180000000
+    sig0 = (i64 vmctx, i32 uext) system_v
+    sig1 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v
+    sig2 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+    sig3 = (i64 vmctx, i32 uext) -> i32 uext system_v
+    stack_limit = gv2
+
+block0(v0 ! mem(mt0, 0x0, 0x0): i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32, v9: i32, v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32, v16: i32, v17: i32, v18: i32, v19: i32, v20: i32, v21: i32):
+    v77 -> v0
+    v81 -> v0
+    v85 -> v0
+    v87 -> v0
+    v91 -> v0
+    v95 -> v0
+    v97 -> v0
+    v130 -> v0
+    v78 = load.i32 notrap aligned table v77+176
+    v127 = iconst.i32 0
+    v79 = icmp eq v78, v127  ; v127 = 0
+    v80 = uextend.i32 v79
+    brif v80, block2, block3
+
+block2:
+    trap unreachable
+
+block3:
+    v82 = load.i32 notrap aligned table v81+176
+    v83 = iconst.i32 1
+    v84 = isub v82, v83  ; v83 = 1
+    store notrap aligned table v84, v85+176
+    jump block4
+
+block4:
+    v88 = load.i32 notrap aligned table v87+176
+    v128 = iconst.i32 0
+    v89 = icmp eq v88, v128  ; v128 = 0
+    v90 = uextend.i32 v89
+    brif v90, block6, block7
+
+block6:
+    trap unreachable
+
+block7:
+    v92 = load.i32 notrap aligned table v91+176
+    v93 = iconst.i32 1
+    v94 = isub v92, v93  ; v93 = 1
+    store notrap aligned table v94, v95+176
+    v96 = iconst.i32 0xffff_3234
+    v98 = load.i64 notrap aligned v97+104
+    v129 = iconst.i64 0x0001_0000
+    v99 = udiv v98, v129  ; v129 = 0x0001_0000
+    v100 = ireduce.i32 v99
+    v101 = iconst.i32 0x0001_0000
+    v102 = imul v100, v101  ; v101 = 0x0001_0000
+    v103 = iconst.i32 0x0005_ffee
+    v104 = iadd v103, v96  ; v103 = 0x0005_ffee, v96 = 0xffff_3234
+    v105 = icmp ule v102, v104
+    v106 = uextend.i32 v105
+    brif v106, block9, block10
+
+block10:
+    v107 = iconst.i32 0
+    v108 = icmp.i32 sle v96, v107  ; v96 = 0xffff_3234, v107 = 0
+    v109 = uextend.i32 v108
+    brif v109, block9, block11
+
+block11:
+    v110 ! range(64, 0x0, 0xffffffff) = uextend.i64 v96  ; v96 = 0xffff_3234
+    v111 ! mem(mt1, 0x0, 0x0) = load.i64 notrap aligned readonly checked v130+96
+    v112 ! mem(mt1, 0x0, 0xffffffff) = iadd v111, v110
+    v113 ! range(64, 0x5ffe6, 0x5ffe6) = iconst.i64 0x0005_ffe6
+    v114 ! mem(mt1, 0x5ffe6, 0x10005ffe5) = iadd v112, v113  ; v113 = 0x0005_ffe6
+    v115 = uload8.i64 little checked heap v114
+    jump block8(v115)
+
+block9:
+    v116 = iconst.i64 0
+    jump block8(v116)  ; v116 = 0
+
+block8(v117: i64):
+    v118 = fcvt_from_uint.f32 v117
+    v86 -> v118
+    jump block5
+
+block5:
+    v121 = iconst.i32 0
+    v22 -> v121
+    v122 = iconst.i32 0
+    v23 -> v122
+    v123 = iconst.i32 0
+    v24 -> v123
+    v124 = iconst.i32 0
+    v25 -> v124
+    v125 = iconst.i32 0
+    v26 -> v125
+    v126 = iconst.i32 0
+    v27 -> v126
+    jump block1
+
+block1:
+    return v22, v23, v24, v25, v26, v27  ; v22 = 0, v23 = 0, v24 = 0, v25 = 0, v26 = 0, v27 = 0
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 8fab74ac6..146f1dd72 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -521,6 +521,15 @@ fn specify_env() -> Result<()> {
         .output()?;
     assert!(output.status.success());
 
+    // Inherit all env vars
+    let output = get_wasmtime_command()?
+        .args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])
+        .env("FOO", "bar")
+        .output()?;
+    assert!(output.status.success());
+    let stdout = String::from_utf8_lossy(&output.stdout);
+    assert!(stdout.contains("FOO=bar"), "bad output: {stdout}");
+
     Ok(())
 }
 
@@ -1490,7 +1499,7 @@ mod test_programs {
             &[
                 "-Ccache=no",
                 "-Wcomponent-model",
-                "-Scommon,http,preview2",
+                "-Scli,http,preview2",
                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
             ],
             None,
diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs
index fd089aab1..791d481d8 100644
--- a/tests/all/stack_overflow.rs
+++ b/tests/all/stack_overflow.rs
@@ -9,35 +9,97 @@ fn host_always_has_some_stack() -> Result<()> {
     // assume hosts always have at least 128k of stack
     const HOST_STACK: usize = 128 * 1024;
 
-    let mut store = Store::<()>::default();
+    let mut store = if cfg!(target_arch = "x86_64") {
+        let mut config = Config::new();
+        // Force cranelift-based libcalls to show up by ensuring that platform
+        // support is turned off.
+        unsafe {
+            config.cranelift_flag_set("has_avx", "false");
+            config.cranelift_flag_set("has_sse42", "false");
+            config.cranelift_flag_set("has_sse41", "false");
+            config.cranelift_flag_set("has_ssse3", "false");
+            config.cranelift_flag_set("has_sse3", "false");
+        }
+        Store::new(&Engine::new(&config)?, ())
+    } else {
+        Store::<()>::default()
+    };
 
     // Create a module that's infinitely recursive, but calls the host on each
     // level of wasm stack to always test how much host stack we have left.
+    //
+    // Each of the function exports of this module calls out to the host in a
+    // different way, and each one is tested below to make sure that the way of
+    // exiting out to the host is tested thoroughly.
     let module = Module::new(
         store.engine(),
         r#"
             (module
-                (import "" "" (func $host))
-                (func $recursive (export "foo")
-                    call $host
-                    call $recursive)
+                (import "" "" (func $host1))
+                (import "" "" (func $host2))
+
+                ;; exit via wasm-to-native trampoline
+                (func $recursive1 (export "f1")
+                    call $host1
+                    call $recursive1)
+
+                ;; exit via wasm-to-array trampoline
+                (func $recursive2 (export "f2")
+                    call $host2
+                    call $recursive2)
+
+                ;; exit via a wasmtime-based libcall
+                (memory 1)
+                (func $recursive3 (export "f3")
+                    (drop (memory.grow (i32.const 0)))
+                    call $recursive3)
+
+                ;; exit via a cranelift-based libcall
+                (func $recursive4 (export "f4")
+                    (drop (call $f32_ceil (f32.const 0)))
+                    call $recursive4)
+                (func $f32_ceil (param f32) (result f32)
+                    (f32.ceil (local.get 0)))
             )
         "#,
     )?;
-    let func = Func::wrap(&mut store, test_host_stack);
-    let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
+    let host1 = Func::wrap(&mut store, test_host_stack);
+    let ty = FuncType::new(store.engine(), [], []);
+    let host2 = Func::new(&mut store, ty, |_, _, _| {
+        test_host_stack();
+        Ok(())
+    });
+    let instance = Instance::new(&mut store, &module, &[host1.into(), host2.into()])?;
+    let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
+    let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
+    let f3 = instance.get_typed_func::<(), ()>(&mut store, "f3")?;
+    let f4 = instance.get_typed_func::<(), ()>(&mut store, "f4")?;
 
     // Make sure that our function traps and the trap says that the call stack
     // has been exhausted.
-    let trap = foo.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    let hits1 = HITS.load(SeqCst);
+    let trap = f1.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits2 = HITS.load(SeqCst);
+    let trap = f2.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits3 = HITS.load(SeqCst);
+    let trap = f3.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits4 = HITS.load(SeqCst);
+    let trap = f4.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::StackOverflow);
+    let hits5 = HITS.load(SeqCst);
 
     // Additionally, however, and this is the crucial test, make sure that the
     // host function actually completed. If HITS is 1 then we entered but didn't
     // exit meaning we segfaulted while executing the host, yet still tried to
     // recover from it with longjmp.
-    assert_eq!(HITS.load(SeqCst), 0);
+    assert_eq!(hits1, 0);
+    assert_eq!(hits2, 0);
+    assert_eq!(hits3, 0);
+    assert_eq!(hits4, 0);
+    assert_eq!(hits5, 0);
 
     return Ok(());
 
diff --git a/tests/disas/icall-simd.wat b/tests/disas/icall-simd.wat
index b312db025..12253e10c 100644
--- a/tests/disas/icall-simd.wat
+++ b/tests/disas/icall-simd.wat
@@ -23,14 +23,13 @@
 ;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i8x16):
 ;; @0033                               v5 = iconst.i32 23
 ;; @0033                               v6 = icmp uge v2, v5  ; v5 = 23
-;; @0033                               trapnz v6, table_oob
 ;; @0033                               v7 = uextend.i64 v2
 ;; @0033                               v8 = global_value.i64 gv4
 ;; @0033                               v9 = ishl_imm v7, 3
 ;; @0033                               v10 = iadd v8, v9
-;; @0033                               v11 = icmp uge v2, v5  ; v5 = 23
-;; @0033                               v12 = select_spectre_guard v11, v8, v10
-;; @0033                               v13 = load.i64 notrap aligned table v12
+;; @0033                               v11 = iconst.i64 0
+;; @0033                               v12 = select_spectre_guard v6, v11, v10  ; v11 = 0
+;; @0033                               v13 = load.i64 table_oob aligned table v12
 ;; @0033                               v14 = band_imm v13, -2
 ;; @0033                               brif v13, block3(v14), block2
 ;;
@@ -43,11 +42,10 @@
 ;; @0033                               jump block3(v20)
 ;;
 ;;                                 block3(v15: i64):
-;; @0033                               trapz v15, icall_null
 ;; @0033                               v21 = global_value.i64 gv3
 ;; @0033                               v22 = load.i64 notrap aligned readonly v21+64
 ;; @0033                               v23 = load.i32 notrap aligned readonly v22
-;; @0033                               v24 = load.i32 notrap aligned readonly v15+24
+;; @0033                               v24 = load.i32 icall_null aligned readonly v15+24
 ;; @0033                               v25 = icmp eq v24, v23
 ;; @0033                               trapz v25, bad_sig
 ;; @0033                               v26 = load.i64 notrap aligned readonly v15+16
diff --git a/tests/disas/icall.wat b/tests/disas/icall.wat
index 69e2fe7b1..e810b78ee 100644
--- a/tests/disas/icall.wat
+++ b/tests/disas/icall.wat
@@ -23,14 +23,13 @@
 ;;                                 block0(v0: i64, v1: i64, v2: i32, v3: f32):
 ;; @0033                               v5 = iconst.i32 23
 ;; @0033                               v6 = icmp uge v2, v5  ; v5 = 23
-;; @0033                               trapnz v6, table_oob
 ;; @0033                               v7 = uextend.i64 v2
 ;; @0033                               v8 = global_value.i64 gv4
 ;; @0033                               v9 = ishl_imm v7, 3
 ;; @0033                               v10 = iadd v8, v9
-;; @0033                               v11 = icmp uge v2, v5  ; v5 = 23
-;; @0033                               v12 = select_spectre_guard v11, v8, v10
-;; @0033                               v13 = load.i64 notrap aligned table v12
+;; @0033                               v11 = iconst.i64 0
+;; @0033                               v12 = select_spectre_guard v6, v11, v10  ; v11 = 0
+;; @0033                               v13 = load.i64 table_oob aligned table v12
 ;; @0033                               v14 = band_imm v13, -2
 ;; @0033                               brif v13, block3(v14), block2
 ;;
@@ -43,11 +42,10 @@
 ;; @0033                               jump block3(v20)
 ;;
 ;;                                 block3(v15: i64):
-;; @0033                               trapz v15, icall_null
 ;; @0033                               v21 = global_value.i64 gv3
 ;; @0033                               v22 = load.i64 notrap aligned readonly v21+64
 ;; @0033                               v23 = load.i32 notrap aligned readonly v22
-;; @0033                               v24 = load.i32 notrap aligned readonly v15+24
+;; @0033                               v24 = load.i32 icall_null aligned readonly v15+24
 ;; @0033                               v25 = icmp eq v24, v23
 ;; @0033                               trapz v25, bad_sig
 ;; @0033                               v26 = load.i64 notrap aligned readonly v15+16
diff --git a/tests/disas/pcc-imported-memory.wat b/tests/disas/pcc-imported-memory.wat
new file mode 100644
index 000000000..5bd75ad01
--- /dev/null
+++ b/tests/disas/pcc-imported-memory.wat
@@ -0,0 +1,78 @@
+;;! target = "x86_64"
+;;! test = "compile"
+;;! flags = [ "-Oopt-level=2", "-Cpcc=y" ]
+
+(module
+  (type (;0;) (func))
+  (import "" "" (memory (;0;) 1))
+  (func (;0;) (type 0)
+    (local i32 i32)
+    memory.size
+    local.set 0
+    block  ;; label = @1
+      block  ;; label = @2
+        memory.size
+        i32.const 65536
+        i32.mul
+        i32.const 4
+        local.get 0
+        i32.add
+        i32.le_u
+        br_if 0 (;@2;)
+        local.get 0
+        i32.const 0
+        i32.le_s
+        br_if 0 (;@2;)
+        local.get 0
+        i32.load align=1
+        local.set 1
+        br 1 (;@1;)
+      end
+      i32.const 0
+      local.set 1
+    end
+    local.get 1
+    drop))
+
+;; function u0:0:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    72(%rdi), %r10
+;;   movq    8(%r10), %rax
+;;   movl    $65536, %r10d
+;;   xorq    %rdx, %rdx, %rdx
+;;   div     %rax, %rdx, %r10, %rax, %rdx ; trap=int_divz
+;;   movq    %rax, %r9
+;;   shll    $16, %r9d, %r9d
+;;   lea     4(%rax), %r10d
+;;   cmpl    %r10d, %r9d
+;;   jbe     label1; j label2
+;; block1:
+;;   jmp     label5
+;; block2:
+;;   testl   %eax, %eax
+;;   jle     label3; j label4
+;; block3:
+;;   jmp     label5
+;; block4:
+;;   movq    72(%rdi), %rcx
+;;   movq    0(%rcx), %rcx
+;;   movl    %eax, %edx
+;;   movl    0(%rcx,%rdx,1), %edi
+;;   jmp     label6
+;; block5:
+;;   xorl    %edi, %edi, %edi
+;;   jmp     label6
+;; block6:
+;;   jmp     label7
+;; block7:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
diff --git a/tests/disas/pcc-loads-x64-avx.wat b/tests/disas/pcc-loads-x64-avx.wat
new file mode 100644
index 000000000..45f9ec872
--- /dev/null
+++ b/tests/disas/pcc-loads-x64-avx.wat
@@ -0,0 +1,95 @@
+;;! target = "x86_64"
+;;! test = "compile"
+;;! flags = [ "-Oopt-level=2", "-Cpcc=y", "-Ccranelift-has-avx=true" ]
+
+(module
+  (memory 1 1)
+  (func (export "load_f32") (param i32) (result f32)
+    local.get 0
+    f32.load)
+  (func (export "load_f64") (param i32) (result f64)
+    local.get 0
+    f64.load)
+  (func (export "store_f32") (param i32 f32)
+    local.get 0
+    local.get 1
+    f32.store)
+  (func (export "store_f64") (param i32 f64)
+    local.get 0
+    local.get 1
+    f64.store))
+;; function u0:0:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   vmovss  0(%r9,%r10,1), %xmm0
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:1:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   vmovsd  0(%r9,%r10,1), %xmm0
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:2:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   vmovss  %xmm0, 0(%r9,%r10,1)
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:3:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   vmovsd  %xmm0, 0(%r9,%r10,1)
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
diff --git a/tests/disas/pcc-loads-x64.wat b/tests/disas/pcc-loads-x64.wat
new file mode 100644
index 000000000..b31030eef
--- /dev/null
+++ b/tests/disas/pcc-loads-x64.wat
@@ -0,0 +1,95 @@
+;;! target = "x86_64"
+;;! test = "compile"
+;;! flags = [ "-Oopt-level=2", "-Cpcc=y", "-Ccranelift-has-avx=false" ]
+
+(module
+  (memory 1 1)
+  (func (export "load_f32") (param i32) (result f32)
+    local.get 0
+    f32.load)
+  (func (export "load_f64") (param i32) (result f64)
+    local.get 0
+    f64.load)
+  (func (export "store_f32") (param i32 f32)
+    local.get 0
+    local.get 1
+    f32.store)
+  (func (export "store_f64") (param i32 f64)
+    local.get 0
+    local.get 1
+    f64.store))
+;; function u0:0:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   movss   0(%r9,%r10,1), %xmm0
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:1:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   movsd   0(%r9,%r10,1), %xmm0
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:2:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   movss   %xmm0, 0(%r9,%r10,1)
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
+;;
+;; function u0:3:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    80(%rdi), %r9
+;;   movl    %edx, %r10d
+;;   movsd   %xmm0, 0(%r9,%r10,1)
+;;   jmp     label1
+;; block1:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
diff --git a/tests/disas/table-get-fixed-size.wat b/tests/disas/table-get-fixed-size.wat
index b0e7dd4c2..fbdaf794d 100644
--- a/tests/disas/table-get-fixed-size.wat
+++ b/tests/disas/table-get-fixed-size.wat
@@ -33,20 +33,14 @@
 ;; @0052                               v3 = iconst.i32 0
 ;; @0054                               v4 = iconst.i32 7
 ;; @0054                               v5 = icmp uge v3, v4  ; v3 = 0, v4 = 7
-;; @0054                               brif v5, block6, block7
-;;
-;;                                 block6 cold:
-;; @0054                               trap table_oob
-;;
-;;                                 block7:
 ;; @0054                               v6 = uextend.i64 v3  ; v3 = 0
 ;; @0054                               v7 = load.i64 notrap aligned v25+72
 ;;                                     v26 = iconst.i64 3
 ;; @0054                               v8 = ishl v6, v26  ; v26 = 3
 ;; @0054                               v9 = iadd v7, v8
-;; @0054                               v10 = icmp.i32 uge v3, v4  ; v3 = 0, v4 = 7
-;; @0054                               v11 = select_spectre_guard v10, v7, v9
-;; @0054                               v12 = load.r64 notrap aligned table v11
+;; @0054                               v10 = iconst.i64 0
+;; @0054                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @0054                               v12 = load.r64 table_oob aligned table v11
 ;;                                     v2 -> v12
 ;; @0054                               v13 = is_null v12
 ;; @0054                               brif v13, block2, block3
@@ -99,20 +93,14 @@
 ;;                                     v25 -> v0
 ;; @005b                               v4 = iconst.i32 7
 ;; @005b                               v5 = icmp uge v2, v4  ; v4 = 7
-;; @005b                               brif v5, block6, block7
-;;
-;;                                 block6 cold:
-;; @005b                               trap table_oob
-;;
-;;                                 block7:
 ;; @005b                               v6 = uextend.i64 v2
 ;; @005b                               v7 = load.i64 notrap aligned v25+72
 ;;                                     v26 = iconst.i64 3
 ;; @005b                               v8 = ishl v6, v26  ; v26 = 3
 ;; @005b                               v9 = iadd v7, v8
-;; @005b                               v10 = icmp.i32 uge v2, v4  ; v4 = 7
-;; @005b                               v11 = select_spectre_guard v10, v7, v9
-;; @005b                               v12 = load.r64 notrap aligned table v11
+;; @005b                               v10 = iconst.i64 0
+;; @005b                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @005b                               v12 = load.r64 table_oob aligned table v11
 ;;                                     v3 -> v12
 ;; @005b                               v13 = is_null v12
 ;; @005b                               brif v13, block2, block3
diff --git a/tests/disas/table-get.wat b/tests/disas/table-get.wat
index 992d62451..0cbee62e2 100644
--- a/tests/disas/table-get.wat
+++ b/tests/disas/table-get.wat
@@ -34,20 +34,14 @@
 ;; @0051                               v3 = iconst.i32 0
 ;; @0053                               v4 = load.i32 notrap aligned v25+80
 ;; @0053                               v5 = icmp uge v3, v4  ; v3 = 0
-;; @0053                               brif v5, block6, block7
-;;
-;;                                 block6 cold:
-;; @0053                               trap table_oob
-;;
-;;                                 block7:
 ;; @0053                               v6 = uextend.i64 v3  ; v3 = 0
 ;; @0053                               v7 = load.i64 notrap aligned v26+72
 ;;                                     v27 = iconst.i64 3
 ;; @0053                               v8 = ishl v6, v27  ; v27 = 3
 ;; @0053                               v9 = iadd v7, v8
-;; @0053                               v10 = icmp.i32 uge v3, v4  ; v3 = 0
-;; @0053                               v11 = select_spectre_guard v10, v7, v9
-;; @0053                               v12 = load.r64 notrap aligned table v11
+;; @0053                               v10 = iconst.i64 0
+;; @0053                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @0053                               v12 = load.r64 table_oob aligned table v11
 ;;                                     v2 -> v12
 ;; @0053                               v13 = is_null v12
 ;; @0053                               brif v13, block2, block3
@@ -102,20 +96,14 @@
 ;;                                     v26 -> v0
 ;; @005a                               v4 = load.i32 notrap aligned v25+80
 ;; @005a                               v5 = icmp uge v2, v4
-;; @005a                               brif v5, block6, block7
-;;
-;;                                 block6 cold:
-;; @005a                               trap table_oob
-;;
-;;                                 block7:
 ;; @005a                               v6 = uextend.i64 v2
 ;; @005a                               v7 = load.i64 notrap aligned v26+72
 ;;                                     v27 = iconst.i64 3
 ;; @005a                               v8 = ishl v6, v27  ; v27 = 3
 ;; @005a                               v9 = iadd v7, v8
-;; @005a                               v10 = icmp.i32 uge v2, v4
-;; @005a                               v11 = select_spectre_guard v10, v7, v9
-;; @005a                               v12 = load.r64 notrap aligned table v11
+;; @005a                               v10 = iconst.i64 0
+;; @005a                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @005a                               v12 = load.r64 table_oob aligned table v11
 ;;                                     v3 -> v12
 ;; @005a                               v13 = is_null v12
 ;; @005a                               brif v13, block2, block3
diff --git a/tests/disas/table-set-fixed-size.wat b/tests/disas/table-set-fixed-size.wat
index ceb2da584..f501bf1b7 100644
--- a/tests/disas/table-set-fixed-size.wat
+++ b/tests/disas/table-set-fixed-size.wat
@@ -34,22 +34,16 @@
 ;; @0052                               v3 = iconst.i32 0
 ;; @0056                               v4 = iconst.i32 7
 ;; @0056                               v5 = icmp uge v3, v4  ; v3 = 0, v4 = 7
-;; @0056                               brif v5, block7, block8
-;;
-;;                                 block7 cold:
-;; @0056                               trap table_oob
-;;
-;;                                 block8:
 ;; @0056                               v6 = uextend.i64 v3  ; v3 = 0
 ;; @0056                               v7 = load.i64 notrap aligned v23+72
 ;;                                     v24 = iconst.i64 3
 ;; @0056                               v8 = ishl v6, v24  ; v24 = 3
 ;; @0056                               v9 = iadd v7, v8
-;; @0056                               v10 = icmp.i32 uge v3, v4  ; v3 = 0, v4 = 7
-;; @0056                               v11 = select_spectre_guard v10, v7, v9
-;; @0056                               v12 = load.i64 notrap aligned table v11
-;; @0056                               store.r64 notrap aligned table v2, v11
-;; @0056                               v13 = is_null.r64 v2
+;; @0056                               v10 = iconst.i64 0
+;; @0056                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @0056                               v12 = load.i64 table_oob aligned table v11
+;; @0056                               store notrap aligned table v2, v11
+;; @0056                               v13 = is_null v2
 ;; @0056                               brif v13, block3, block2
 ;;
 ;;                                 block2:
@@ -102,22 +96,16 @@
 ;;                                     v23 -> v0
 ;; @005f                               v4 = iconst.i32 7
 ;; @005f                               v5 = icmp uge v2, v4  ; v4 = 7
-;; @005f                               brif v5, block7, block8
-;;
-;;                                 block7 cold:
-;; @005f                               trap table_oob
-;;
-;;                                 block8:
 ;; @005f                               v6 = uextend.i64 v2
 ;; @005f                               v7 = load.i64 notrap aligned v23+72
 ;;                                     v24 = iconst.i64 3
 ;; @005f                               v8 = ishl v6, v24  ; v24 = 3
 ;; @005f                               v9 = iadd v7, v8
-;; @005f                               v10 = icmp.i32 uge v2, v4  ; v4 = 7
-;; @005f                               v11 = select_spectre_guard v10, v7, v9
-;; @005f                               v12 = load.i64 notrap aligned table v11
-;; @005f                               store.r64 notrap aligned table v3, v11
-;; @005f                               v13 = is_null.r64 v3
+;; @005f                               v10 = iconst.i64 0
+;; @005f                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @005f                               v12 = load.i64 table_oob aligned table v11
+;; @005f                               store notrap aligned table v3, v11
+;; @005f                               v13 = is_null v3
 ;; @005f                               brif v13, block3, block2
 ;;
 ;;                                 block2:
diff --git a/tests/disas/table-set.wat b/tests/disas/table-set.wat
index 93b71d52a..f792fb23a 100644
--- a/tests/disas/table-set.wat
+++ b/tests/disas/table-set.wat
@@ -35,22 +35,16 @@
 ;; @0051                               v3 = iconst.i32 0
 ;; @0055                               v4 = load.i32 notrap aligned v23+80
 ;; @0055                               v5 = icmp uge v3, v4  ; v3 = 0
-;; @0055                               brif v5, block7, block8
-;;
-;;                                 block7 cold:
-;; @0055                               trap table_oob
-;;
-;;                                 block8:
 ;; @0055                               v6 = uextend.i64 v3  ; v3 = 0
 ;; @0055                               v7 = load.i64 notrap aligned v24+72
 ;;                                     v25 = iconst.i64 3
 ;; @0055                               v8 = ishl v6, v25  ; v25 = 3
 ;; @0055                               v9 = iadd v7, v8
-;; @0055                               v10 = icmp.i32 uge v3, v4  ; v3 = 0
-;; @0055                               v11 = select_spectre_guard v10, v7, v9
-;; @0055                               v12 = load.i64 notrap aligned table v11
-;; @0055                               store.r64 notrap aligned table v2, v11
-;; @0055                               v13 = is_null.r64 v2
+;; @0055                               v10 = iconst.i64 0
+;; @0055                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @0055                               v12 = load.i64 table_oob aligned table v11
+;; @0055                               store notrap aligned table v2, v11
+;; @0055                               v13 = is_null v2
 ;; @0055                               brif v13, block3, block2
 ;;
 ;;                                 block2:
@@ -105,22 +99,16 @@
 ;;                                     v24 -> v0
 ;; @005e                               v4 = load.i32 notrap aligned v23+80
 ;; @005e                               v5 = icmp uge v2, v4
-;; @005e                               brif v5, block7, block8
-;;
-;;                                 block7 cold:
-;; @005e                               trap table_oob
-;;
-;;                                 block8:
 ;; @005e                               v6 = uextend.i64 v2
 ;; @005e                               v7 = load.i64 notrap aligned v24+72
 ;;                                     v25 = iconst.i64 3
 ;; @005e                               v8 = ishl v6, v25  ; v25 = 3
 ;; @005e                               v9 = iadd v7, v8
-;; @005e                               v10 = icmp.i32 uge v2, v4
-;; @005e                               v11 = select_spectre_guard v10, v7, v9
-;; @005e                               v12 = load.i64 notrap aligned table v11
-;; @005e                               store.r64 notrap aligned table v3, v11
-;; @005e                               v13 = is_null.r64 v3
+;; @005e                               v10 = iconst.i64 0
+;; @005e                               v11 = select_spectre_guard v5, v10, v9  ; v10 = 0
+;; @005e                               v12 = load.i64 table_oob aligned table v11
+;; @005e                               store notrap aligned table v3, v11
+;; @005e                               v13 = is_null v3
 ;; @005e                               brif v13, block3, block2
 ;;
 ;;                                 block2:
diff --git a/tests/disas/typed-funcrefs.wat b/tests/disas/typed-funcrefs.wat
new file mode 100644
index 000000000..10ba6a955
--- /dev/null
+++ b/tests/disas/typed-funcrefs.wat
@@ -0,0 +1,311 @@
+;;! target = "x86_64"
+;;! test = "optimize"
+;;! flags = [ "-Wfunction-references=y" ]
+
+;; This test is meant to simulate how typed funcrefs in a table may be
+;; used for ICs (inline caches) in a Wasm module compiled from a dynamic
+;; language. In native JIT engines, IC chains have head pointers that
+;; are raw code pointers and IC-using code can call each with a few ops
+;; (load pointer, call indirect). We'd like similar efficiency by
+;; storing funcrefs for the first IC in each chain in a typed-funcref
+;; table.
+
+(module
+  (type $ic-stub (func (param i32 i32 i32 i32) (result i32)))
+
+  ;; This syntax declares a table that is exactly 100 elements, whose
+  ;; elements are nullable function references, and whose default
+  ;; value is `null`.
+  (table $ic-sites 100 100 (ref null $ic-stub))
+
+  (func $ic1 (param i32 i32 i32 i32) (result i32)
+        local.get 0)
+
+  ;; A function which uses ICs through `table.get` plus `call_ref`
+  (func $call-ics-with-call-ref (param i32 i32 i32 i32) (result i32)
+        (local $sum i32)
+
+        ;; IC callsite index 1 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 1
+        table.get $ic-sites
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        ;; IC callsite index 2 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 2
+        table.get $ic-sites
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        local.get $sum)
+
+  ;; Same as the above function, but uses `call_indirect` rather than
+  ;; `call_ref`.
+  (func $call-ics-with-call-indirect (param i32 i32 i32 i32) (result i32)
+        (local $sum i32)
+
+        ;; IC callsite index 1 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 1
+        call_indirect $ic-sites (type $ic-stub)
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        ;; IC callsite index 2 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 2
+        call_indirect $ic-sites (type $ic-stub)
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        local.get $sum)
+
+  (global $ic-site0 (mut (ref $ic-stub)) (ref.func $ic1))
+  (global $ic-site1 (mut (ref $ic-stub)) (ref.func $ic1))
+
+  ;; Sort of similar to the previous two functions, but uses globals instead of
+  ;; tables to store ICs. Mostly just here for comparison in terms of codegen.
+  (func $call-ics-with-global-get (param i32 i32 i32 i32) (result i32)
+        (local $sum i32)
+
+        ;; IC callsite index 1 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        global.get $ic-site0
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        ;; IC callsite index 2 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        global.get $ic-site1
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        local.get $sum)
+)
+
+;; function u0:0(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     sig0 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig1 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v6 -> v2
+;; @0039                               jump block1
+;;
+;;                                 block1:
+;; @0039                               return v2
+;; }
+;;
+;; function u0:1(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     gv4 = load.i64 notrap aligned gv3+72
+;;     sig0 = (i64 vmctx, i32 uext, i32 uext) -> i64 system_v
+;;     sig1 = (i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast
+;;     sig2 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig3 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v21 -> v0
+;;                                     v47 -> v0
+;;                                     v56 -> v0
+;;                                     v59 -> v0
+;;                                     v30 -> v2
+;;                                     v31 -> v3
+;;                                     v32 -> v4
+;;                                     v33 -> v5
+;; @0048                               v12 = load.i64 notrap aligned v0+72
+;;                                     v62 = iconst.i8 0
+;; @0048                               v15 = iconst.i64 0
+;;                                     v70 = iconst.i64 8
+;; @0048                               v14 = iadd v12, v70  ; v70 = 8
+;; @0048                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
+;; @0048                               v17 = load.i64 table_oob aligned table v16
+;;                                     v58 = iconst.i64 -2
+;; @0048                               v18 = band v17, v58  ; v58 = -2
+;; @0048                               brif v17, block3(v18), block2
+;;
+;;                                 block2 cold:
+;; @005b                               v48 = load.i64 notrap aligned readonly v0+56
+;; @005b                               v49 = load.i64 notrap aligned readonly v48+72
+;; @003c                               v7 = iconst.i32 0
+;;                                     v28 -> v7
+;; @0046                               v8 = iconst.i32 1
+;; @0048                               v24 = call_indirect sig0, v49(v0, v7, v8)  ; v7 = 0, v8 = 1
+;; @0048                               jump block3(v24)
+;;
+;;                                 block3(v19: i64):
+;; @004a                               v25 = load.i64 null_reference aligned readonly v19+16
+;; @004a                               v26 = load.i64 notrap aligned readonly v19+32
+;; @004a                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
+;; @005b                               v38 = load.i64 notrap aligned v0+72
+;;                                     v79 = iconst.i8 0
+;;                                     v80 = iconst.i64 0
+;;                                     v78 = iconst.i64 16
+;; @005b                               v40 = iadd v38, v78  ; v78 = 16
+;; @005b                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
+;; @005b                               v43 = load.i64 table_oob aligned table v42
+;;                                     v81 = iconst.i64 -2
+;;                                     v82 = band v43, v81  ; v81 = -2
+;; @005b                               brif v43, block5(v82), block4
+;;
+;;                                 block4 cold:
+;;                                     v83 = load.i64 notrap aligned readonly v0+56
+;;                                     v84 = load.i64 notrap aligned readonly v83+72
+;;                                     v85 = iconst.i32 0
+;; @0059                               v34 = iconst.i32 2
+;; @005b                               v50 = call_indirect sig0, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @005b                               jump block5(v50)
+;;
+;;                                 block5(v45: i64):
+;; @005d                               v51 = load.i64 null_reference aligned readonly v45+16
+;; @005d                               v52 = load.i64 notrap aligned readonly v45+32
+;; @005d                               v53 = call_indirect sig1, v51(v52, v0, v2, v3, v4, v5)
+;; @0066                               jump block1
+;;
+;;                                 block1:
+;; @0061                               v55 = iadd.i32 v53, v27
+;;                                     v6 -> v55
+;; @0066                               return v55
+;; }
+;;
+;; function u0:2(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     gv4 = load.i64 notrap aligned gv3+72
+;;     sig0 = (i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast
+;;     sig1 = (i64 vmctx, i32 uext, i32 uext) -> i64 system_v
+;;     sig2 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig3 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v21 -> v0
+;;                                     v47 -> v0
+;;                                     v56 -> v0
+;;                                     v59 -> v0
+;;                                     v30 -> v2
+;;                                     v31 -> v3
+;;                                     v32 -> v4
+;;                                     v33 -> v5
+;; @0075                               v12 = load.i64 notrap aligned v0+72
+;;                                     v62 = iconst.i8 0
+;; @0075                               v15 = iconst.i64 0
+;;                                     v70 = iconst.i64 8
+;; @0075                               v14 = iadd v12, v70  ; v70 = 8
+;; @0075                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
+;; @0075                               v17 = load.i64 table_oob aligned table v16
+;;                                     v58 = iconst.i64 -2
+;; @0075                               v18 = band v17, v58  ; v58 = -2
+;; @0075                               brif v17, block3(v18), block2
+;;
+;;                                 block2 cold:
+;; @0087                               v48 = load.i64 notrap aligned readonly v0+56
+;; @0087                               v49 = load.i64 notrap aligned readonly v48+72
+;; @0069                               v7 = iconst.i32 0
+;;                                     v28 -> v7
+;; @0073                               v8 = iconst.i32 1
+;; @0075                               v24 = call_indirect sig1, v49(v0, v7, v8)  ; v7 = 0, v8 = 1
+;; @0075                               jump block3(v24)
+;;
+;;                                 block3(v19: i64):
+;; @0075                               v25 = load.i64 icall_null aligned readonly v19+16
+;; @0075                               v26 = load.i64 notrap aligned readonly v19+32
+;; @0075                               v27 = call_indirect sig0, v25(v26, v0, v2, v3, v4, v5)
+;; @0087                               v38 = load.i64 notrap aligned v0+72
+;;                                     v79 = iconst.i8 0
+;;                                     v80 = iconst.i64 0
+;;                                     v78 = iconst.i64 16
+;; @0087                               v40 = iadd v38, v78  ; v78 = 16
+;; @0087                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
+;; @0087                               v43 = load.i64 table_oob aligned table v42
+;;                                     v81 = iconst.i64 -2
+;;                                     v82 = band v43, v81  ; v81 = -2
+;; @0087                               brif v43, block5(v82), block4
+;;
+;;                                 block4 cold:
+;;                                     v83 = load.i64 notrap aligned readonly v0+56
+;;                                     v84 = load.i64 notrap aligned readonly v83+72
+;;                                     v85 = iconst.i32 0
+;; @0085                               v34 = iconst.i32 2
+;; @0087                               v50 = call_indirect sig1, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @0087                               jump block5(v50)
+;;
+;;                                 block5(v45: i64):
+;; @0087                               v51 = load.i64 icall_null aligned readonly v45+16
+;; @0087                               v52 = load.i64 notrap aligned readonly v45+32
+;; @0087                               v53 = call_indirect sig0, v51(v52, v0, v2, v3, v4, v5)
+;; @0091                               jump block1
+;;
+;;                                 block1:
+;; @008c                               v55 = iadd.i32 v53, v27
+;;                                     v6 -> v55
+;; @0091                               return v55
+;; }
+;;
+;; function u0:3(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast
+;;     sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v8 -> v0
+;;                                     v14 -> v0
+;; @009e                               v9 = load.i64 notrap aligned table v0+96
+;; @00a0                               v10 = load.i64 null_reference aligned readonly v9+16
+;; @00a0                               v11 = load.i64 notrap aligned readonly v9+32
+;; @00a0                               v12 = call_indirect sig0, v10(v11, v0, v2, v3, v4, v5)
+;; @00af                               v15 = load.i64 notrap aligned table v0+112
+;; @00b1                               v16 = load.i64 null_reference aligned readonly v15+16
+;; @00b1                               v17 = load.i64 notrap aligned readonly v15+32
+;; @00b1                               v18 = call_indirect sig0, v16(v17, v0, v2, v3, v4, v5)
+;; @00ba                               jump block1
+;;
+;;                                 block1:
+;; @00b5                               v19 = iadd.i32 v18, v12
+;;                                     v6 -> v19
+;; @00ba                               return v19
+;; }
diff --git a/tests/misc_testsuite/function-references/call_indirect.wast b/tests/misc_testsuite/function-references/call_indirect.wast
new file mode 100644
index 000000000..60fabf9c6
--- /dev/null
+++ b/tests/misc_testsuite/function-references/call_indirect.wast
@@ -0,0 +1,54 @@
+(module
+  (table $t1 2 funcref)
+  (elem (table $t1) (i32.const 0) func $nop)
+  (func $nop)
+
+  (func (export "t1") (param i32)
+    local.get 0
+    call_indirect $t1)
+  (func (export "t1-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t1 (param i32))
+
+  (type $empty (func))
+  (table $t2 2 (ref null $empty))
+  (elem (table $t2) (i32.const 0) (ref null $empty) (ref.func $nop))
+
+  (func (export "t2") (param i32)
+    local.get 0
+    call_indirect $t2)
+  (func (export "t2-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t2 (param i32))
+
+  (table $t3 2 (ref $empty) (ref.func $nop))
+
+  (func (export "t3") (param i32)
+    local.get 0
+    call_indirect $t3)
+  (func (export "t3-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t3 (param i32))
+)
+
+(assert_return (invoke "t1" (i32.const 0)))
+(assert_trap (invoke "t1" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t1" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t1-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t1-wrong-type" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t1-wrong-type" (i32.const 2)) "out of bounds")
+(assert_return (invoke "t2" (i32.const 0)))
+(assert_trap (invoke "t2" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t2" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t2-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t2-wrong-type" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t2-wrong-type" (i32.const 2)) "out of bounds")
+(assert_return (invoke "t3" (i32.const 0)))
+(assert_return (invoke "t3" (i32.const 1)))
+(assert_trap (invoke "t3" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t3-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t3-wrong-type" (i32.const 1)) "call type mismatch")
+(assert_trap (invoke "t3-wrong-type" (i32.const 2)) "out of bounds")
diff --git a/tests/misc_testsuite/simd/canonicalize-nan.wast b/tests/misc_testsuite/simd/canonicalize-nan.wast
index b35a941f0..8a0ae71d9 100644
--- a/tests/misc_testsuite/simd/canonicalize-nan.wast
+++ b/tests/misc_testsuite/simd/canonicalize-nan.wast
@@ -39,6 +39,32 @@
     f64.reinterpret_i64
     f32.demote_f64
     i32.reinterpret_f32)
+
+  (func (export "reinterpret-and-promote") (param i32) (result i64)
+    local.get 0
+    f32.reinterpret_i32
+    f64.promote_f32
+    i64.reinterpret_f64)
+
+  (func (export "copysign-and-demote") (param f64) (result f32)
+    local.get 0
+    f64.const -0x1
+    f64.copysign
+    f32.demote_f64)
+
+  (func (export "copysign-and-promote") (param f32) (result f64)
+    local.get 0
+    f32.const -0x1
+    f32.copysign
+    f64.promote_f32)
+
+  (func (export "f32x4.demote_f64x2_zero") (param v128) (result v128)
+    local.get 0
+    f32x4.demote_f64x2_zero)
+
+  (func (export "f64x2.promote_low_f32x4") (param v128) (result v128)
+    local.get 0
+    f64x2.promote_low_f32x4)
 )
 
 (assert_return (invoke "f32x4.floor" (v128.const f32x4 1 -2.2 3.4 nan))
@@ -65,3 +91,17 @@
 
 (assert_return (invoke "reinterpret-and-demote" (i64.const 0xfffefdfccccdcecf))
                (i32.const 0x7fc00000))
+(assert_return (invoke "reinterpret-and-promote" (i32.const 0xfffefdfc))
+               (i64.const 0x7ff8000000000000))
+(assert_return (invoke "copysign-and-demote" (f64.const nan))
+               (f32.const nan:0x7fc00000))
+(assert_return (invoke "copysign-and-promote" (f32.const nan))
+               (f64.const nan:0x7ff8000000000000))
+
+(assert_return (invoke "f32x4.demote_f64x2_zero"
+               (v128.const i64x2 0xfffefdfccccdcecf 0xfffefdfccccdcecf))
+               (v128.const f32x4 nan:0x7fc00000 nan:0x7fc00000 0 0))
+
+(assert_return (invoke "f64x2.promote_low_f32x4"
+               (v128.const i32x4 0xfffefdfc 0xfffefdfc 0 0))
+               (v128.const f64x2 nan:0x7ff8000000000000 nan:0x7ff8000000000000))
diff --git a/tests/spec_testsuite b/tests/spec_testsuite
index 5a1a59060..16a839d56 160000
--- a/tests/spec_testsuite
+++ b/tests/spec_testsuite
@@ -1,1 +1,1 @@
-Subproject commit 5a1a590603d81f40ef471abba70a90a9ae5f4627
+Subproject commit 16a839d5601c283541a84572b47637f035b51437
diff --git a/winch/filetests/filetests/x64/load/grow_load.wat b/winch/filetests/filetests/x64/load/grow_load.wat
new file mode 100644
index 000000000..1e1bf5e51
--- /dev/null
+++ b/winch/filetests/filetests/x64/load/grow_load.wat
@@ -0,0 +1,103 @@
+;;! target = "x86_64"
+(module
+  (type (;0;) (func (param f32 f64 i64 i64 f64 f64 f32) (result f32 f64 f32)))
+  (func (;0;) (type 0) (param f32 f64 i64 i64 f64 f64 f32) (result f32 f64 f32)
+    global.get 1
+    i32.eqz
+    if ;; label = @1
+      unreachable
+    end
+    global.get 1
+    i32.const 1
+    i32.sub
+    global.set 1
+    memory.size
+    memory.grow
+    i64.load8_s offset=143396
+    (drop)
+    (f32.const 0)
+    (f64.const 0)
+    (f32.const 0)
+  )
+  (memory (;1;) 10 10)
+  (global (;0;) f32 f32.const 0x1.d6a0d6p+87 (;=284477330000000000000000000;))
+  (global (;1;) (mut i32) i32.const 1000)
+  (export "main" (func 0))
+  (export "0" (memory 0))
+  (export "1" (global 0))
+)
+;;      	 55                   	push	rbp
+;;      	 4889e5               	mov	rbp, rsp
+;;      	 4c8b5f08             	mov	r11, qword ptr [rdi + 8]
+;;      	 4d8b1b               	mov	r11, qword ptr [r11]
+;;      	 4981c360000000       	add	r11, 0x60
+;;      	 4939e3               	cmp	r11, rsp
+;;      	 0f87f1000000         	ja	0x10c
+;;   1b:	 4989fe               	mov	r14, rdi
+;;      	 4883ec50             	sub	rsp, 0x50
+;;      	 48897c2448           	mov	qword ptr [rsp + 0x48], rdi
+;;      	 4889742440           	mov	qword ptr [rsp + 0x40], rsi
+;;      	 f30f1144243c         	movss	dword ptr [rsp + 0x3c], xmm0
+;;      	 f20f114c2430         	movsd	qword ptr [rsp + 0x30], xmm1
+;;      	 4889542428           	mov	qword ptr [rsp + 0x28], rdx
+;;      	 48894c2420           	mov	qword ptr [rsp + 0x20], rcx
+;;      	 f20f11542418         	movsd	qword ptr [rsp + 0x18], xmm2
+;;      	 f20f115c2410         	movsd	qword ptr [rsp + 0x10], xmm3
+;;      	 f30f1164240c         	movss	dword ptr [rsp + 0xc], xmm4
+;;      	 4c890424             	mov	qword ptr [rsp], r8
+;;      	 418b4670             	mov	eax, dword ptr [r14 + 0x70]
+;;      	 83f800               	cmp	eax, 0
+;;      	 b800000000           	mov	eax, 0
+;;      	 400f94c0             	sete	al
+;;      	 85c0                 	test	eax, eax
+;;      	 0f8402000000         	je	0x72
+;;   70:	 0f0b                 	ud2	
+;;      	 418b4670             	mov	eax, dword ptr [r14 + 0x70]
+;;      	 83e801               	sub	eax, 1
+;;      	 41894670             	mov	dword ptr [r14 + 0x70], eax
+;;      	 498b4658             	mov	rax, qword ptr [r14 + 0x58]
+;;      	 c1e810               	shr	eax, 0x10
+;;      	 4883ec04             	sub	rsp, 4
+;;      	 890424               	mov	dword ptr [rsp], eax
+;;      	 4d8b5e38             	mov	r11, qword ptr [r14 + 0x38]
+;;      	 498b0b               	mov	rcx, qword ptr [r11]
+;;      	 4883ec0c             	sub	rsp, 0xc
+;;      	 4c89f7               	mov	rdi, r14
+;;      	 8b74240c             	mov	esi, dword ptr [rsp + 0xc]
+;;      	 ba00000000           	mov	edx, 0
+;;      	 ffd1                 	call	rcx
+;;      	 4883c40c             	add	rsp, 0xc
+;;      	 4883c404             	add	rsp, 4
+;;      	 4c8b742448           	mov	r14, qword ptr [rsp + 0x48]
+;;      	 89c0                 	mov	eax, eax
+;;      	 498b4e50             	mov	rcx, qword ptr [r14 + 0x50]
+;;      	 4801c1               	add	rcx, rax
+;;      	 4881c124300200       	add	rcx, 0x23024
+;;      	 480fbe01             	movsx	rax, byte ptr [rcx]
+;;      	 f30f100543000000     	movss	xmm0, dword ptr [rip + 0x43]
+;;      	 4883ec0c             	sub	rsp, 0xc
+;;      	 f2440f103d3e000000   	
+;; 				movsd	xmm15, qword ptr [rip + 0x3e]
+;;      	 f2440f113c24         	movsd	qword ptr [rsp], xmm15
+;;      	 f3440f103d27000000   	
+;; 				movss	xmm15, dword ptr [rip + 0x27]
+;;      	 f3440f117c2408       	movss	dword ptr [rsp + 8], xmm15
+;;      	 488b44240c           	mov	rax, qword ptr [rsp + 0xc]
+;;      	 415b                 	pop	r11
+;;      	 4c8918               	mov	qword ptr [rax], r11
+;;      	 448b1c24             	mov	r11d, dword ptr [rsp]
+;;      	 4883c404             	add	rsp, 4
+;;      	 44895808             	mov	dword ptr [rax + 8], r11d
+;;      	 4883c450             	add	rsp, 0x50
+;;      	 5d                   	pop	rbp
+;;      	 c3                   	ret	
+;;  10c:	 0f0b                 	ud2	
+;;  10e:	 0000                 	add	byte ptr [rax], al
+;;  110:	 0000                 	add	byte ptr [rax], al
+;;  112:	 0000                 	add	byte ptr [rax], al
+;;  114:	 0000                 	add	byte ptr [rax], al
+;;  116:	 0000                 	add	byte ptr [rax], al
+;;  118:	 0000                 	add	byte ptr [rax], al
+;;  11a:	 0000                 	add	byte ptr [rax], al
+;;  11c:	 0000                 	add	byte ptr [rax], al
+;;  11e:	 0000                 	add	byte ptr [rax], al