Repository: bytecodealliance/wasmtime
Pull request #6042 (2023-03-17 10:55:59)

Various cleanups to Layout
This removes some dead code, replaces some function calls with more specific function calls and renames a couple of items to have a clearer name.


Patch:
diff --git a/cranelift/codegen/meta/src/gen_inst.rs b/cranelift/codegen/meta/src/gen_inst.rs
index 488296999..f3de73b57 100644
--- a/cranelift/codegen/meta/src/gen_inst.rs
+++ b/cranelift/codegen/meta/src/gen_inst.rs
@@ -602,7 +602,22 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
             "side_effects_idempotent",
             "Despite having side effects, is this instruction okay to GVN?",
             fmt,
-        )
+        );
+
+        // Generate an opcode list, for iterating over all known opcodes.
+        fmt.doc_comment("All cranelift opcodes.");
+        fmt.line("pub fn all() -> &'static [Opcode] {");
+        fmt.indent(|fmt| {
+            fmt.line("return &[");
+            for inst in all_inst {
+                fmt.indent(|fmt| {
+                    fmtln!(fmt, "Opcode::{},", inst.camel_name);
+                });
+            }
+            fmt.line("];");
+        });
+        fmt.line("}");
+        fmt.empty_line();
     });
     fmt.line("}");
     fmt.empty_line();
diff --git a/cranelift/codegen/src/dominator_tree.rs b/cranelift/codegen/src/dominator_tree.rs
index a4985261a..135697497 100644
--- a/cranelift/codegen/src/dominator_tree.rs
+++ b/cranelift/codegen/src/dominator_tree.rs
@@ -2,7 +2,7 @@
 
 use crate::entity::SecondaryMap;
 use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
-use crate::ir::{Block, ExpandedProgramPoint, Function, Inst, Layout, ProgramOrder, Value};
+use crate::ir::{Block, Function, Inst, Layout, ProgramPoint};
 use crate::packed_option::PackedOption;
 use crate::timing;
 use alloc::vec::Vec;
@@ -88,7 +88,7 @@ impl DominatorTree {
     }
 
     /// Compare two blocks relative to the reverse post-order.
-    fn rpo_cmp_block(&self, a: Block, b: Block) -> Ordering {
+    pub fn rpo_cmp_block(&self, a: Block, b: Block) -> Ordering {
         self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number)
     }
 
@@ -100,13 +100,13 @@ impl DominatorTree {
     /// If `a` and `b` belong to the same block, compare their relative position in the block.
     pub fn rpo_cmp<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering
     where
-        A: Into<ExpandedProgramPoint>,
-        B: Into<ExpandedProgramPoint>,
+        A: Into<ProgramPoint>,
+        B: Into<ProgramPoint>,
     {
         let a = a.into();
         let b = b.into();
         self.rpo_cmp_block(layout.pp_block(a), layout.pp_block(b))
-            .then(layout.cmp(a, b))
+            .then(layout.pp_cmp(a, b))
     }
 
     /// Returns `true` if `a` dominates `b`.
@@ -120,21 +120,21 @@ impl DominatorTree {
     /// An instruction is considered to dominate itself.
     pub fn dominates<A, B>(&self, a: A, b: B, layout: &Layout) -> bool
     where
-        A: Into<ExpandedProgramPoint>,
-        B: Into<ExpandedProgramPoint>,
+        A: Into<ProgramPoint>,
+        B: Into<ProgramPoint>,
     {
         let a = a.into();
         let b = b.into();
         match a {
-            ExpandedProgramPoint::Block(block_a) => {
+            ProgramPoint::Block(block_a) => {
                 a == b || self.last_dominator(block_a, b, layout).is_some()
             }
-            ExpandedProgramPoint::Inst(inst_a) => {
+            ProgramPoint::Inst(inst_a) => {
                 let block_a = layout
                     .inst_block(inst_a)
                     .expect("Instruction not in layout.");
                 match self.last_dominator(block_a, b, layout) {
-                    Some(last) => layout.cmp(inst_a, last) != Ordering::Greater,
+                    Some(last) => layout.pp_cmp(inst_a, last) != Ordering::Greater,
                     None => false,
                 }
             }
@@ -145,11 +145,11 @@ impl DominatorTree {
     /// If no instructions in `a` dominate `b`, return `None`.
     pub fn last_dominator<B>(&self, a: Block, b: B, layout: &Layout) -> Option<Inst>
     where
-        B: Into<ExpandedProgramPoint>,
+        B: Into<ProgramPoint>,
     {
         let (mut block_b, mut inst_b) = match b.into() {
-            ExpandedProgramPoint::Block(block) => (block, None),
-            ExpandedProgramPoint::Inst(inst) => (
+            ProgramPoint::Block(block) => (block, None),
+            ProgramPoint::Inst(inst) => (
                 layout.inst_block(inst).expect("Instruction not in layout."),
                 Some(inst),
             ),
@@ -210,7 +210,7 @@ impl DominatorTree {
         );
 
         // We're in the same block. The common dominator is the earlier instruction.
-        if layout.cmp(a.inst, b.inst) == Ordering::Less {
+        if layout.pp_cmp(a.inst, b.inst) == Ordering::Less {
             a
         } else {
             b
@@ -475,7 +475,9 @@ impl DominatorTreePreorder {
         // sibling lists are ordered according to the CFG reverse post-order.
         for &block in domtree.cfg_postorder() {
             if let Some(idom_inst) = domtree.idom(block) {
-                let idom = layout.pp_block(idom_inst);
+                let idom = layout
+                    .inst_block(idom_inst)
+                    .expect("Instruction not in layout.");
                 let sib = mem::replace(&mut self.nodes[idom].child, block.into());
                 self.nodes[block].sibling = sib;
             } else {
@@ -505,7 +507,9 @@ impl DominatorTreePreorder {
         // its dominator tree children.
         for &block in domtree.cfg_postorder() {
             if let Some(idom_inst) = domtree.idom(block) {
-                let idom = layout.pp_block(idom_inst);
+                let idom = layout
+                    .inst_block(idom_inst)
+                    .expect("Instruction not in layout.");
                 let pre_max = cmp::max(self.nodes[block].pre_max, self.nodes[idom].pre_max);
                 self.nodes[idom].pre_max = pre_max;
             }
@@ -568,26 +572,13 @@ impl DominatorTreePreorder {
     /// program points dominated by pp follow immediately and contiguously after pp in the order.
     pub fn pre_cmp<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering
     where
-        A: Into<ExpandedProgramPoint>,
-        B: Into<ExpandedProgramPoint>,
+        A: Into<ProgramPoint>,
+        B: Into<ProgramPoint>,
     {
         let a = a.into();
         let b = b.into();
         self.pre_cmp_block(layout.pp_block(a), layout.pp_block(b))
-            .then(layout.cmp(a, b))
-    }
-
-    /// Compare two value defs according to the dominator tree pre-order.
-    ///
-    /// Two values defined at the same program point are compared according to their parameter or
-    /// result order.
-    ///
-    /// This is a total ordering of the values in the function.
-    pub fn pre_cmp_def(&self, a: Value, b: Value, func: &Function) -> Ordering {
-        let da = func.dfg.value_def(a);
-        let db = func.dfg.value_def(b);
-        self.pre_cmp(da, db, &func.layout)
-            .then_with(|| da.num().cmp(&db.num()))
+            .then(layout.pp_cmp(a, b))
     }
 }
 
diff --git a/cranelift/codegen/src/ir/dfg.rs b/cranelift/codegen/src/ir/dfg.rs
index 171892161..5784812cf 100644
--- a/cranelift/codegen/src/ir/dfg.rs
+++ b/cranelift/codegen/src/ir/dfg.rs
@@ -492,11 +492,6 @@ impl ValueDef {
         }
     }
 
-    /// Get the program point where the value was defined.
-    pub fn pp(self) -> ir::ExpandedProgramPoint {
-        self.into()
-    }
-
     /// Get the number component of this definition.
     ///
     /// When multiple values are defined at the same program point, this indicates the index of
diff --git a/cranelift/codegen/src/ir/layout.rs b/cranelift/codegen/src/ir/layout.rs
index 644665bab..f8bbc12fe 100644
--- a/cranelift/codegen/src/ir/layout.rs
+++ b/cranelift/codegen/src/ir/layout.rs
@@ -4,7 +4,7 @@
 //! determined by the `Layout` data structure defined in this module.
 
 use crate::entity::SecondaryMap;
-use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder};
+use crate::ir::progpoint::ProgramPoint;
 use crate::ir::{Block, Inst};
 use crate::packed_option::PackedOption;
 use crate::{timing, trace};
@@ -114,33 +114,33 @@ fn test_midpoint() {
     assert_eq!(midpoint(3, 4), None);
 }
 
-impl ProgramOrder for Layout {
-    fn cmp<A, B>(&self, a: A, b: B) -> cmp::Ordering
+impl Layout {
+    /// Compare the program points `a` and `b` relative to this program order.
+    ///
+    /// Return `Less` if `a` appears in the program before `b`.
+    ///
+    /// This is declared as a generic such that it can be called with `Inst` and `Block` arguments
+    /// directly. Depending on the implementation, there is a good chance performance will be
+    /// improved for those cases where the type of either argument is known statically.
+    pub fn pp_cmp<A, B>(&self, a: A, b: B) -> cmp::Ordering
     where
-        A: Into<ExpandedProgramPoint>,
-        B: Into<ExpandedProgramPoint>,
+        A: Into<ProgramPoint>,
+        B: Into<ProgramPoint>,
     {
         let a_seq = self.seq(a);
         let b_seq = self.seq(b);
         a_seq.cmp(&b_seq)
     }
-
-    fn is_block_gap(&self, inst: Inst, block: Block) -> bool {
-        let i = &self.insts[inst];
-        let e = &self.blocks[block];
-
-        i.next.is_none() && i.block == e.prev
-    }
 }
 
 // Private methods for dealing with sequence numbers.
 impl Layout {
     /// Get the sequence number of a program point that must correspond to an entity in the layout.
-    fn seq<PP: Into<ExpandedProgramPoint>>(&self, pp: PP) -> SequenceNumber {
+    fn seq<PP: Into<ProgramPoint>>(&self, pp: PP) -> SequenceNumber {
         // When `PP = Inst` or `PP = Block`, we expect this dynamic type check to be optimized out.
         match pp.into() {
-            ExpandedProgramPoint::Block(block) => self.blocks[block].seq,
-            ExpandedProgramPoint::Inst(inst) => self.insts[inst].seq,
+            ProgramPoint::Block(block) => self.blocks[block].seq,
+            ProgramPoint::Inst(inst) => self.insts[inst].seq,
         }
     }
 
@@ -536,15 +536,10 @@ impl Layout {
     }
 
     /// Get the block containing the program point `pp`. Panic if `pp` is not in the layout.
-    pub fn pp_block<PP>(&self, pp: PP) -> Block
-    where
-        PP: Into<ExpandedProgramPoint>,
-    {
-        match pp.into() {
-            ExpandedProgramPoint::Block(block) => block,
-            ExpandedProgramPoint::Inst(inst) => {
-                self.inst_block(inst).expect("Program point not in layout")
-            }
+    pub fn pp_block(&self, pp: ProgramPoint) -> Block {
+        match pp {
+            ProgramPoint::Block(block) => block,
+            ProgramPoint::Inst(inst) => self.inst_block(inst).expect("Program point not in layout"),
         }
     }
 
@@ -867,7 +862,7 @@ mod tests {
     use super::Layout;
     use crate::cursor::{Cursor, CursorPosition};
     use crate::entity::EntityRef;
-    use crate::ir::{Block, Inst, ProgramOrder, SourceLoc};
+    use crate::ir::{Block, Inst, SourceLoc};
     use alloc::vec::Vec;
     use core::cmp::Ordering;
 
@@ -1289,13 +1284,8 @@ mod tests {
         }
 
         // Check `ProgramOrder`.
-        assert_eq!(layout.cmp(e2, e2), Ordering::Equal);
-        assert_eq!(layout.cmp(e2, i2), Ordering::Less);
-        assert_eq!(layout.cmp(i3, i2), Ordering::Greater);
-
-        assert_eq!(layout.is_block_gap(i1, e2), true);
-        assert_eq!(layout.is_block_gap(i3, e1), true);
-        assert_eq!(layout.is_block_gap(i1, e1), false);
-        assert_eq!(layout.is_block_gap(i2, e1), false);
+        assert_eq!(layout.pp_cmp(e2, e2), Ordering::Equal);
+        assert_eq!(layout.pp_cmp(e2, i2), Ordering::Less);
+        assert_eq!(layout.pp_cmp(i3, i2), Ordering::Greater)
     }
 }
diff --git a/cranelift/codegen/src/ir/mod.rs b/cranelift/codegen/src/ir/mod.rs
index 7b000c8e7..3a5936fcb 100644
--- a/cranelift/codegen/src/ir/mod.rs
+++ b/cranelift/codegen/src/ir/mod.rs
@@ -53,7 +53,7 @@ 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::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint};
+pub use crate::ir::progpoint::ProgramPoint;
 pub use crate::ir::sourceloc::RelSourceLoc;
 pub use crate::ir::sourceloc::SourceLoc;
 pub use crate::ir::stackslot::{
diff --git a/cranelift/codegen/src/ir/progpoint.rs b/cranelift/codegen/src/ir/progpoint.rs
index 39c4d98fb..780054776 100644
--- a/cranelift/codegen/src/ir/progpoint.rs
+++ b/cranelift/codegen/src/ir/progpoint.rs
@@ -1,10 +1,7 @@
 //! Program points.
 
-use crate::entity::EntityRef;
-use crate::ir::{Block, Inst, ValueDef};
-use core::cmp;
+use crate::ir::{Block, Inst};
 use core::fmt;
-use core::u32;
 
 /// A `ProgramPoint` represents a position in a function where the live range of an SSA value can
 /// begin or end. It can be either:
@@ -14,45 +11,14 @@ use core::u32;
 ///
 /// This corresponds more or less to the lines in the textual form of Cranelift IR.
 #[derive(PartialEq, Eq, Clone, Copy)]
-pub struct ProgramPoint(u32);
-
-impl From<Inst> for ProgramPoint {
-    fn from(inst: Inst) -> Self {
-        let idx = inst.index();
-        debug_assert!(idx < (u32::MAX / 2) as usize);
-        Self((idx * 2) as u32)
-    }
-}
-
-impl From<Block> for ProgramPoint {
-    fn from(block: Block) -> Self {
-        let idx = block.index();
-        debug_assert!(idx < (u32::MAX / 2) as usize);
-        Self((idx * 2 + 1) as u32)
-    }
-}
-
-impl From<ValueDef> for ProgramPoint {
-    fn from(def: ValueDef) -> Self {
-        match def {
-            ValueDef::Result(inst, _) => inst.into(),
-            ValueDef::Param(block, _) => block.into(),
-            ValueDef::Union(_, _) => panic!("Union does not have a single program point"),
-        }
-    }
-}
-
-/// An expanded program point directly exposes the variants, but takes twice the space to
-/// represent.
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub enum ExpandedProgramPoint {
+pub enum ProgramPoint {
     /// An instruction in the function.
     Inst(Inst),
     /// A block header.
     Block(Block),
 }
 
-impl ExpandedProgramPoint {
+impl ProgramPoint {
     /// Get the instruction we know is inside.
     pub fn unwrap_inst(self) -> Inst {
         match self {
@@ -62,39 +28,19 @@ impl ExpandedProgramPoint {
     }
 }
 
-impl From<Inst> for ExpandedProgramPoint {
+impl From<Inst> for ProgramPoint {
     fn from(inst: Inst) -> Self {
         Self::Inst(inst)
     }
 }
 
-impl From<Block> for ExpandedProgramPoint {
+impl From<Block> for ProgramPoint {
     fn from(block: Block) -> Self {
         Self::Block(block)
     }
 }
 
-impl From<ValueDef> for ExpandedProgramPoint {
-    fn from(def: ValueDef) -> Self {
-        match def {
-            ValueDef::Result(inst, _) => inst.into(),
-            ValueDef::Param(block, _) => block.into(),
-            ValueDef::Union(_, _) => panic!("Union does not have a single program point"),
-        }
-    }
-}
-
-impl From<ProgramPoint> for ExpandedProgramPoint {
-    fn from(pp: ProgramPoint) -> Self {
-        if pp.0 & 1 == 0 {
-            Self::Inst(Inst::from_u32(pp.0 / 2))
-        } else {
-            Self::Block(Block::from_u32(pp.0 / 2))
-        }
-    }
-}
-
-impl fmt::Display for ExpandedProgramPoint {
+impl fmt::Display for ProgramPoint {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
             Self::Inst(x) => write!(f, "{}", x),
@@ -103,48 +49,12 @@ impl fmt::Display for ExpandedProgramPoint {
     }
 }
 
-impl fmt::Display for ProgramPoint {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let epp: ExpandedProgramPoint = (*self).into();
-        epp.fmt(f)
-    }
-}
-
-impl fmt::Debug for ExpandedProgramPoint {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "ExpandedProgramPoint({})", self)
-    }
-}
-
 impl fmt::Debug for ProgramPoint {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "ProgramPoint({})", self)
     }
 }
 
-/// Context for ordering program points.
-///
-/// `ProgramPoint` objects don't carry enough information to be ordered independently, they need a
-/// context providing the program order.
-pub trait ProgramOrder {
-    /// Compare the program points `a` and `b` relative to this program order.
-    ///
-    /// Return `Less` if `a` appears in the program before `b`.
-    ///
-    /// This is declared as a generic such that it can be called with `Inst` and `Block` arguments
-    /// directly. Depending on the implementation, there is a good chance performance will be
-    /// improved for those cases where the type of either argument is known statically.
-    fn cmp<A, B>(&self, a: A, b: B) -> cmp::Ordering
-    where
-        A: Into<ExpandedProgramPoint>,
-        B: Into<ExpandedProgramPoint>;
-
-    /// Is the range from `inst` to `block` just the gap between consecutive blocks?
-    ///
-    /// This returns true if `inst` is the terminator in the block immediately before `block`.
-    fn is_block_gap(&self, inst: Inst, block: Block) -> bool;
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle
index fbaac1da3..e51c9e891 100644
--- a/cranelift/codegen/src/isa/x64/inst.isle
+++ b/cranelift/codegen/src/isa/x64/inst.isle
@@ -1715,14 +1715,11 @@
         (if-let $true (value32_zeros_upper32 src))
         src)
 
-;; The `extractlane` instruction, extended to `$I32`, means that either an
-;; i8x16 or an i16x8 is being extracted. These are implemented with
-;; the `pextr{b,w}` instruction which automatically zero the upper bits of the
-;; destination register so the `uextend` in these cases can be elided.
-;;
-;; TODO: the documentation for `pextr{b,w}` seems to indicate it zero extends
-;; to not only 32-bits but probably the whole 64-bit register. If that's the
-;; case then this should match a zero-extend to any size instead of just `$I32`.
+;; The `extractlane` instruction, when paired with a zero-extension, means
+;; that one of `i{8x16,16x8,32x4}` is being extracted and extend. These
+;; extractions are implemented with the `pextr{b,w,d}` instructions which
+;; automatically zero the upper bits of the destination register so the
+;; `uextend` in these cases can be elided.
 ;;
 ;; TODO: the interaction here between this rule and the "it's written far away"
 ;; rule to lower `extractlane` isn't great. Ideally this rule (and the other
@@ -1730,7 +1727,7 @@
 ;; be connected to the extractlane rules. There's some discussion of this on
 ;; #6022 but the gist is that there's not a lot of great options at this time,
 ;; so this doc block is what's here for now.
-(rule 1 (extend_to_gpr src @ (extractlane _ _) $I32 (ExtendKind.Zero))
+(rule 1 (extend_to_gpr src @ (extractlane _ _) _ (ExtendKind.Zero))
         src)
 
 (rule (extend_to_gpr (and val (value_type from_ty))
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index f42270f12..98f1b1375 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -3808,6 +3808,8 @@
 (rule 0 (lower (extractlane val @ (value_type ty @ (multi_lane 16 8)) (u8_from_uimm8 lane)))
       (x64_pextrw val lane))
 
+;; See the note in the 8x16 case above for how this rule is connected to
+;; `extend_to_gpr`.
 (rule 0 (lower (extractlane val @ (value_type ty @ (multi_lane 32 4)) (u8_from_uimm8 lane)))
       (x64_pextrd val lane))
 
diff --git a/cranelift/codegen/src/legalizer/mod.rs b/cranelift/codegen/src/legalizer/mod.rs
index c226f0cb5..3b09ed1fc 100644
--- a/cranelift/codegen/src/legalizer/mod.rs
+++ b/cranelift/codegen/src/legalizer/mod.rs
@@ -299,7 +299,10 @@ fn expand_cond_trap(
     //
     //   new_block_resume:
     //     ..
-    let old_block = func.layout.pp_block(inst);
+    let old_block = func
+        .layout
+        .inst_block(inst)
+        .expect("Instruction not in layout.");
     let new_block_trap = func.dfg.make_block();
     let new_block_resume = func.dfg.make_block();
 
diff --git a/cranelift/codegen/src/opts/algebraic.isle b/cranelift/codegen/src/opts/algebraic.isle
index ce04c2716..3efa05dfe 100644
--- a/cranelift/codegen/src/opts/algebraic.isle
+++ b/cranelift/codegen/src/opts/algebraic.isle
@@ -35,6 +35,9 @@
 ;; ineg(ineg(x)) == x.
 (rule (simplify (ineg ty (ineg ty x))) (subsume x))
 
+;; x-x == 0.
+(rule (simplify (isub (fits_in_64 (ty_int ty)) x x)) (subsume (iconst ty (imm64 0))))
+
 ;; x*1 == 1*x == x.
 (rule (simplify (imul ty
                       x
diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs
index 016abdbf7..4f1e8a3df 100644
--- a/cranelift/codegen/src/verifier/mod.rs
+++ b/cranelift/codegen/src/verifier/mod.rs
@@ -884,7 +884,11 @@ impl<'a> Verifier<'a> {
         self.verify_value(loc_inst, v, errors)?;
 
         let dfg = &self.func.dfg;
-        let loc_block = self.func.layout.pp_block(loc_inst);
+        let loc_block = self
+            .func
+            .layout
+            .inst_block(loc_inst)
+            .expect("Instruction not in layout.");
         let is_reachable = self.expected_domtree.is_reachable(loc_block);
 
         // SSA form
@@ -1101,17 +1105,13 @@ impl<'a> Verifier<'a> {
                 ));
             }
         }
-        // We verify rpo_cmp on pairs of adjacent blocks in the postorder
+        // We verify rpo_cmp_block on pairs of adjacent blocks in the postorder
         for (&prev_block, &next_block) in domtree.cfg_postorder().iter().adjacent_pairs() {
-            if self
-                .expected_domtree
-                .rpo_cmp(prev_block, next_block, &self.func.layout)
-                != Ordering::Greater
-            {
+            if self.expected_domtree.rpo_cmp_block(prev_block, next_block) != Ordering::Greater {
                 return errors.fatal((
                     next_block,
                     format!(
-                        "invalid domtree, rpo_cmp does not says {} is greater than {}",
+                        "invalid domtree, rpo_cmp_block does not says {} is greater than {}",
                         prev_block, next_block
                     ),
                 ));
diff --git a/cranelift/frontend/src/frontend.rs b/cranelift/frontend/src/frontend.rs
index a6b32f661..8c320d23a 100644
--- a/cranelift/frontend/src/frontend.rs
+++ b/cranelift/frontend/src/frontend.rs
@@ -1095,9 +1095,6 @@ impl<'a> FunctionBuilder<'a> {
     }
 
     fn handle_ssa_side_effects(&mut self, side_effects: SideEffects) {
-        for split_block in side_effects.split_blocks_created {
-            self.func_ctx.status[split_block] = BlockStatus::Filled;
-        }
         for modified_block in side_effects.instructions_added_to_blocks {
             if self.is_pristine(modified_block) {
                 self.func_ctx.status[modified_block] = BlockStatus::Partial;
diff --git a/cranelift/frontend/src/ssa.rs b/cranelift/frontend/src/ssa.rs
index 8e1615479..584e6cc96 100644
--- a/cranelift/frontend/src/ssa.rs
+++ b/cranelift/frontend/src/ssa.rs
@@ -63,9 +63,6 @@ pub struct SSABuilder {
 /// Side effects of a `use_var` or a `seal_block` method call.
 #[derive(Default)]
 pub struct SideEffects {
-    /// When we want to append jump arguments to a `br_table` instruction, the critical edge is
-    /// splitted and the newly created `Block`s are signaled here.
-    pub split_blocks_created: Vec<Block>,
     /// When a variable is used but has never been defined before (this happens in the case of
     /// unreachable code), a placeholder `iconst` or `fconst` value is added to the right `Block`.
     /// This field signals if it is the case and return the `Block` to which the initialization has
@@ -75,7 +72,7 @@ pub struct SideEffects {
 
 impl SideEffects {
     fn is_empty(&self) -> bool {
-        self.split_blocks_created.is_empty() && self.instructions_added_to_blocks.is_empty()
+        self.instructions_added_to_blocks.is_empty()
     }
 }
 
diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs
index e48301b27..d51f008f9 100644
--- a/cranelift/fuzzgen/src/function_generator.rs
+++ b/cranelift/fuzzgen/src/function_generator.rs
@@ -415,6 +415,7 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
         Architecture::X86_64 => {
             exceptions!(
                 (Opcode::IaddCout, &([I8, I8] | [I16, I16] | [I128, I128])),
+                (Opcode::Imul, &[I8X16, I8X16]),
                 // https://github.com/bytecodealliance/wasmtime/issues/5468
                 (Opcode::Smulhi | Opcode::Umulhi, &[I8, I8]),
                 // https://github.com/bytecodealliance/wasmtime/issues/4756
@@ -680,6 +681,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Iadd, &[I32, I32], &[I32]),
     (Opcode::Iadd, &[I64, I64], &[I64]),
     (Opcode::Iadd, &[I128, I128], &[I128]),
+    (Opcode::Iadd, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Iadd, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Iadd, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Iadd, &[I64X2, I64X2], &[I64X2]),
     // IaddCout
     (Opcode::IaddCout, &[I8, I8], &[I8, I8]),
     (Opcode::IaddCout, &[I16, I16], &[I16, I8]),
@@ -702,6 +707,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Isub, &[I32, I32], &[I32]),
     (Opcode::Isub, &[I64, I64], &[I64]),
     (Opcode::Isub, &[I128, I128], &[I128]),
+    (Opcode::Isub, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Isub, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Isub, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Isub, &[I64X2, I64X2], &[I64X2]),
     // UsubSat
     (Opcode::UsubSat, &[I8X16, I8X16], &[I8X16]),
     (Opcode::UsubSat, &[I16X8, I16X8], &[I16X8]),
@@ -718,6 +727,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Imul, &[I32, I32], &[I32]),
     (Opcode::Imul, &[I64, I64], &[I64]),
     (Opcode::Imul, &[I128, I128], &[I128]),
+    (Opcode::Imul, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Imul, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Imul, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Imul, &[I64X2, I64X2], &[I64X2]),
     // Smulhi
     (Opcode::Smulhi, &[I8, I8], &[I8]),
     (Opcode::Smulhi, &[I16, I16], &[I16]),
@@ -991,6 +1004,12 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Band, &[I128, I128], &[I128]),
     (Opcode::Band, &[F32, F32], &[F32]),
     (Opcode::Band, &[F64, F64], &[F64]),
+    (Opcode::Band, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Band, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Band, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Band, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::Band, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Band, &[F64X2, F64X2], &[F64X2]),
     // Bor
     (Opcode::Bor, &[I8, I8], &[I8]),
     (Opcode::Bor, &[I16, I16], &[I16]),
@@ -999,6 +1018,12 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Bor, &[I128, I128], &[I128]),
     (Opcode::Bor, &[F32, F32], &[F32]),
     (Opcode::Bor, &[F64, F64], &[F64]),
+    (Opcode::Bor, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Bor, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Bor, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Bor, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::Bor, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Bor, &[F64X2, F64X2], &[F64X2]),
     // Bxor
     (Opcode::Bxor, &[I8, I8], &[I8]),
     (Opcode::Bxor, &[I16, I16], &[I16]),
@@ -1007,14 +1032,26 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Bxor, &[I128, I128], &[I128]),
     (Opcode::Bxor, &[F32, F32], &[F32]),
     (Opcode::Bxor, &[F64, F64], &[F64]),
+    (Opcode::Bxor, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::Bxor, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::Bxor, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::Bxor, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::Bxor, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Bxor, &[F64X2, F64X2], &[F64X2]),
     // Bnot
-    (Opcode::Bnot, &[I8, I8], &[I8]),
-    (Opcode::Bnot, &[I16, I16], &[I16]),
-    (Opcode::Bnot, &[I32, I32], &[I32]),
-    (Opcode::Bnot, &[I64, I64], &[I64]),
-    (Opcode::Bnot, &[I128, I128], &[I128]),
-    (Opcode::Bnot, &[F32, F32], &[F32]),
-    (Opcode::Bnot, &[F64, F64], &[F64]),
+    (Opcode::Bnot, &[I8], &[I8]),
+    (Opcode::Bnot, &[I16], &[I16]),
+    (Opcode::Bnot, &[I32], &[I32]),
+    (Opcode::Bnot, &[I64], &[I64]),
+    (Opcode::Bnot, &[I128], &[I128]),
+    (Opcode::Bnot, &[F32], &[F32]),
+    (Opcode::Bnot, &[F64], &[F64]),
+    (Opcode::Bnot, &[I8X16], &[I8X16]),
+    (Opcode::Bnot, &[I16X8], &[I16X8]),
+    (Opcode::Bnot, &[I32X4], &[I32X4]),
+    (Opcode::Bnot, &[I64X2], &[I64X2]),
+    (Opcode::Bnot, &[F32X4], &[F32X4]),
+    (Opcode::Bnot, &[F64X2], &[F64X2]),
     // BandNot
     (Opcode::BandNot, &[I8, I8], &[I8]),
     (Opcode::BandNot, &[I16, I16], &[I16]),
@@ -1023,6 +1060,12 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::BandNot, &[I128, I128], &[I128]),
     (Opcode::BandNot, &[F32, F32], &[F32]),
     (Opcode::BandNot, &[F64, F64], &[F64]),
+    (Opcode::BandNot, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::BandNot, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::BandNot, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::BandNot, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::BandNot, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::BandNot, &[F64X2, F64X2], &[F64X2]),
     // BorNot
     (Opcode::BorNot, &[I8, I8], &[I8]),
     (Opcode::BorNot, &[I16, I16], &[I16]),
@@ -1031,6 +1074,12 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::BorNot, &[I128, I128], &[I128]),
     (Opcode::BorNot, &[F32, F32], &[F32]),
     (Opcode::BorNot, &[F64, F64], &[F64]),
+    (Opcode::BorNot, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::BorNot, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::BorNot, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::BorNot, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::BorNot, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::BorNot, &[F64X2, F64X2], &[F64X2]),
     // BxorNot
     (Opcode::BxorNot, &[I8, I8], &[I8]),
     (Opcode::BxorNot, &[I16, I16], &[I16]),
@@ -1039,6 +1088,12 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::BxorNot, &[I128, I128], &[I128]),
     (Opcode::BxorNot, &[F32, F32], &[F32]),
     (Opcode::BxorNot, &[F64, F64], &[F64]),
+    (Opcode::BxorNot, &[I8X16, I8X16], &[I8X16]),
+    (Opcode::BxorNot, &[I16X8, I16X8], &[I16X8]),
+    (Opcode::BxorNot, &[I32X4, I32X4], &[I32X4]),
+    (Opcode::BxorNot, &[I64X2, I64X2], &[I64X2]),
+    (Opcode::BxorNot, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::BxorNot, &[F64X2, F64X2], &[F64X2]),
     // Bitrev
     (Opcode::Bitrev, &[I8], &[I8]),
     (Opcode::Bitrev, &[I16], &[I16]),
@@ -1114,6 +1169,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     (Opcode::Bitselect, &[I32, I32, I32], &[I32]),
     (Opcode::Bitselect, &[I64, I64, I64], &[I64]),
     (Opcode::Bitselect, &[I128, I128, I128], &[I128]),
+    (Opcode::Bitselect, &[I8X16, I8X16, I8X16], &[I8X16]),
+    (Opcode::Bitselect, &[I16X8, I16X8, I16X8], &[I16X8]),
+    (Opcode::Bitselect, &[I32X4, I32X4, I32X4], &[I32X4]),
+    (Opcode::Bitselect, &[I64X2, I64X2, I64X2], &[I64X2]),
     // Select
     (Opcode::Select, &[I8, I8, I8], &[I8]),
     (Opcode::Select, &[I8, I16, I16], &[I16]),
@@ -1169,15 +1228,23 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     // Fadd
     (Opcode::Fadd, &[F32, F32], &[F32]),
     (Opcode::Fadd, &[F64, F64], &[F64]),
+    (Opcode::Fadd, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Fadd, &[F64X2, F64X2], &[F64X2]),
     // Fmul
     (Opcode::Fmul, &[F32, F32], &[F32]),
     (Opcode::Fmul, &[F64, F64], &[F64]),
+    (Opcode::Fmul, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Fmul, &[F64X2, F64X2], &[F64X2]),
     // Fsub
     (Opcode::Fsub, &[F32, F32], &[F32]),
     (Opcode::Fsub, &[F64, F64], &[F64]),
+    (Opcode::Fsub, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Fsub, &[F64X2, F64X2], &[F64X2]),
     // Fdiv
     (Opcode::Fdiv, &[F32, F32], &[F32]),
     (Opcode::Fdiv, &[F64, F64], &[F64]),
+    (Opcode::Fdiv, &[F32X4, F32X4], &[F32X4]),
+    (Opcode::Fdiv, &[F64X2, F64X2], &[F64X2]),
     // Fmin
     (Opcode::Fmin, &[F32, F32], &[F32]),
     (Opcode::Fmin, &[F64, F64], &[F64]),
@@ -1198,27 +1265,43 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
     // Fma
     (Opcode::Fma, &[F32, F32, F32], &[F32]),
     (Opcode::Fma, &[F64, F64, F64], &[F64]),
+    (Opcode::Fma, &[F32X4, F32X4, F32X4], &[F32X4]),
+    (Opcode::Fma, &[F64X2, F64X2, F64X4], &[F64X2]),
     // Fabs
     (Opcode::Fabs, &[F32], &[F32]),
     (Opcode::Fabs, &[F64], &[F64]),
+    (Opcode::Fabs, &[F32X4], &[F32X4]),
+    (Opcode::Fabs, &[F64X2], &[F64X2]),
     // Fneg
     (Opcode::Fneg, &[F32], &[F32]),
     (Opcode::Fneg, &[F64], &[F64]),
+    (Opcode::Fneg, &[F32X4], &[F32X4]),
+    (Opcode::Fneg, &[F64X2], &[F64X2]),
     // Sqrt
     (Opcode::Sqrt, &[F32], &[F32]),
     (Opcode::Sqrt, &[F64], &[F64]),
+    (Opcode::Sqrt, &[F32X4], &[F32X4]),
+    (Opcode::Sqrt, &[F64X2], &[F64X2]),
     // Ceil
     (Opcode::Ceil, &[F32], &[F32]),
     (Opcode::Ceil, &[F64], &[F64]),
+    (Opcode::Ceil, &[F32X4], &[F32X4]),
+    (Opcode::Ceil, &[F64X2], &[F64X2]),
     // Floor
     (Opcode::Floor, &[F32], &[F32]),
     (Opcode::Floor, &[F64], &[F64]),
+    (Opcode::Floor, &[F32X4], &[F32X4]),
+    (Opcode::Floor, &[F64X2], &[F64X2]),
     // Trunc
     (Opcode::Trunc, &[F32], &[F32]),
     (Opcode::Trunc, &[F64], &[F64]),
+    (Opcode::Trunc, &[F32X4], &[F32X4]),
+    (Opcode::Trunc, &[F64X2], &[F64X2]),
     // Nearest
     (Opcode::Nearest, &[F32], &[F32]),
     (Opcode::Nearest, &[F64], &[F64]),
+    (Opcode::Nearest, &[F32X4], &[F32X4]),
+    (Opcode::Nearest, &[F64X2], &[F64X2]),
     // Fpromote
     (Opcode::Fpromote, &[F32], &[F64]),
     // Fdemote
diff --git a/crates/fuzzing/src/generators/memory.rs b/crates/fuzzing/src/generators/memory.rs
index 57ad4dd41..bdf679b58 100644
--- a/crates/fuzzing/src/generators/memory.rs
+++ b/crates/fuzzing/src/generators/memory.rs
@@ -2,6 +2,7 @@
 
 use anyhow::Result;
 use arbitrary::{Arbitrary, Unstructured};
+use std::ops::Range;
 use wasmtime::{LinearMemory, MemoryCreator, MemoryType};
 
 /// Configuration for linear memories in Wasmtime.
@@ -86,6 +87,12 @@ unsafe impl LinearMemory for UnalignedMemory {
         // of memory is always unaligned.
         self.src[1..].as_ptr() as *mut _
     }
+
+    fn wasm_accessible(&self) -> Range<usize> {
+        let base = self.as_ptr() as usize;
+        let len = self.byte_size();
+        base..base + len
+    }
 }
 
 /// A mechanism to generate [`UnalignedMemory`] at runtime.
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index eabe27794..c3c6d6378 100644
--- a/crates/runtime/src/instance.rs
+++ b/crates/runtime/src/instance.rs
@@ -13,7 +13,7 @@ use crate::vmcontext::{
 };
 use crate::{
     ExportFunction, ExportGlobal, ExportMemory, ExportTable, Imports, ModuleRuntimeInfo, Store,
-    VMFunctionBody, VMSharedSignatureIndex,
+    VMFunctionBody, VMSharedSignatureIndex, WasmFault,
 };
 use anyhow::Error;
 use anyhow::Result;
@@ -1046,6 +1046,23 @@ impl Instance {
             }
         }
     }
+
+    fn wasm_fault(&self, addr: usize) -> Option<WasmFault> {
+        let mut fault = None;
+        for (_, memory) in self.memories.iter() {
+            let accessible = memory.wasm_accessible();
+            if accessible.start <= addr && addr < accessible.end {
+                // All linear memories should be disjoint so assert that no
+                // prior fault has been found.
+                assert!(fault.is_none());
+                fault = Some(WasmFault {
+                    memory_size: memory.byte_size(),
+                    wasm_address: u64::try_from(addr - accessible.start).unwrap(),
+                });
+            }
+        }
+        fault
+    }
 }
 
 impl Drop for Instance {
@@ -1231,4 +1248,15 @@ impl InstanceHandle {
     pub fn initialize(&mut self, module: &Module, is_bulk_memory: bool) -> Result<()> {
         allocator::initialize_instance(self.instance_mut(), module, is_bulk_memory)
     }
+
+    /// Attempts to convert from the host `addr` specified to a WebAssembly
+    /// based address recorded in `WasmFault`.
+    ///
+    /// This method will check all linear memories that this instance contains
+    /// to see if any of them contain `addr`. If one does then `Some` is
+    /// returned with metadata about the wasm fault. Otherwise `None` is
+    /// returned and `addr` doesn't belong to this instance.
+    pub fn wasm_fault(&self, addr: usize) -> Option<WasmFault> {
+        self.instance().wasm_fault(addr)
+    }
 }
diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs
index 05fe1a352..c89d74906 100644
--- a/crates/runtime/src/instance/allocator/pooling.rs
+++ b/crates/runtime/src/instance/allocator/pooling.rs
@@ -817,9 +817,13 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
             // else to come in and map something.
             slot.instantiate(initial_size as usize, image, &plan.style)?;
 
-            memories.push(Memory::new_static(plan, memory, slot, unsafe {
-                &mut *req.store.get().unwrap()
-            })?);
+            memories.push(Memory::new_static(
+                plan,
+                memory,
+                slot,
+                self.memories.memory_and_guard_size,
+                unsafe { &mut *req.store.get().unwrap() },
+            )?);
         }
 
         Ok(())
diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs
index 31b013786..3428a0ffb 100644
--- a/crates/runtime/src/lib.rs
+++ b/crates/runtime/src/lib.rs
@@ -21,6 +21,7 @@
 )]
 
 use anyhow::Error;
+use std::fmt;
 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
 use std::sync::Arc;
 use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex, HostPtr, VMOffsets};
@@ -237,3 +238,22 @@ pub enum WaitResult {
     /// original value matched as expected but nothing ever called `notify`.
     TimedOut = 2,
 }
+
+/// Description about a fault that occurred in WebAssembly.
+#[derive(Debug)]
+pub struct WasmFault {
+    /// The size of memory, in bytes, at the time of the fault.
+    pub memory_size: usize,
+    /// The WebAssembly address at which the fault occurred.
+    pub wasm_address: u64,
+}
+
+impl fmt::Display for WasmFault {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "memory fault at wasm address 0x{:x} in linear memory of size 0x{:x}",
+            self.wasm_address, self.memory_size,
+        )
+    }
+}
diff --git a/crates/runtime/src/memory.rs b/crates/runtime/src/memory.rs
index 3cbcd125d..e3f3d2c56 100644
--- a/crates/runtime/src/memory.rs
+++ b/crates/runtime/src/memory.rs
@@ -9,6 +9,7 @@ use crate::{MemoryImage, MemoryImageSlot, Store, WaitResult};
 use anyhow::Error;
 use anyhow::{bail, format_err, Result};
 use std::convert::TryFrom;
+use std::ops::Range;
 use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
 use std::sync::{Arc, RwLock};
 use std::time::Instant;
@@ -152,6 +153,12 @@ pub trait RuntimeLinearMemory: Send + Sync {
 
     /// Used for optional dynamic downcasting.
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
+
+    /// Returns the range of addresses that may be reached by WebAssembly.
+    ///
+    /// This starts at the base of linear memory and ends at the end of the
+    /// guard pages, if any.
+    fn wasm_accessible(&self) -> Range<usize>;
 }
 
 /// A linear memory instance.
@@ -338,6 +345,12 @@ impl RuntimeLinearMemory for MmapMemory {
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }
+
+    fn wasm_accessible(&self) -> Range<usize> {
+        let base = self.mmap.as_mut_ptr() as usize + self.pre_guard_size;
+        let end = base + (self.mmap.len() - self.pre_guard_size);
+        base..end
+    }
 }
 
 /// A "static" memory where the lifetime of the backing memory is managed
@@ -350,6 +363,10 @@ struct StaticMemory {
     /// The current size, in bytes, of this memory.
     size: usize,
 
+    /// The size, in bytes, of the virtual address allocation starting at `base`
+    /// and going to the end of the guard pages at the end of the linear memory.
+    memory_and_guard_size: usize,
+
     /// The image management, if any, for this memory. Owned here and
     /// returned to the pooling allocator when termination occurs.
     memory_image: MemoryImageSlot,
@@ -361,6 +378,7 @@ impl StaticMemory {
         initial_size: usize,
         maximum_size: Option<usize>,
         memory_image: MemoryImageSlot,
+        memory_and_guard_size: usize,
     ) -> Result<Self> {
         if base.len() < initial_size {
             bail!(
@@ -381,6 +399,7 @@ impl StaticMemory {
             base,
             size: initial_size,
             memory_image,
+            memory_and_guard_size,
         })
     }
 }
@@ -420,6 +439,12 @@ impl RuntimeLinearMemory for StaticMemory {
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }
+
+    fn wasm_accessible(&self) -> Range<usize> {
+        let base = self.base.as_ptr() as usize;
+        let end = base + self.memory_and_guard_size;
+        base..end
+    }
 }
 
 /// For shared memory (and only for shared memory), this lock-version restricts
@@ -620,6 +645,10 @@ impl RuntimeLinearMemory for SharedMemory {
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }
+
+    fn wasm_accessible(&self) -> Range<usize> {
+        self.0.memory.read().unwrap().wasm_accessible()
+    }
 }
 
 /// Representation of a runtime wasm linear memory.
@@ -648,10 +677,12 @@ impl Memory {
         plan: &MemoryPlan,
         base: &'static mut [u8],
         memory_image: MemoryImageSlot,
+        memory_and_guard_size: usize,
         store: &mut dyn Store,
     ) -> Result<Self> {
         let (minimum, maximum) = Self::limit_new(plan, Some(store))?;
-        let pooled_memory = StaticMemory::new(base, minimum, maximum, memory_image)?;
+        let pooled_memory =
+            StaticMemory::new(base, minimum, maximum, memory_image, memory_and_guard_size)?;
         let allocation = Box::new(pooled_memory);
         let allocation: Box<dyn RuntimeLinearMemory> = if plan.memory.shared {
             // FIXME: since the pooling allocator owns the memory allocation
@@ -874,6 +905,13 @@ impl Memory {
             }
         }
     }
+
+    /// Returns the range of bytes that WebAssembly should be able to address in
+    /// this linear memory. Note that this includes guard pages which wasm can
+    /// hit.
+    pub fn wasm_accessible(&self) -> Range<usize> {
+        self.0.wasm_accessible()
+    }
 }
 
 /// In the configurations where bounds checks were elided in JIT code (because
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 215bfddc0..618b6de50 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -147,9 +147,25 @@ pub enum TrapReason {
         needs_backtrace: bool,
     },
 
-    /// A trap raised from Cranelift-generated code with the pc listed of where
-    /// the trap came from.
-    Jit(usize),
+    /// A trap raised from Cranelift-generated code.
+    Jit {
+        /// The program counter where this trap originated.
+        ///
+        /// This is later used with side tables from compilation to translate
+        /// the trapping address to a trap code.
+        pc: usize,
+
+        /// If the trap was a memory-related trap such as SIGSEGV then this
+        /// field will contain the address of the inaccessible data.
+        ///
+        /// Note that wasm loads/stores are not guaranteed to fill in this
+        /// information. Dynamically-bounds-checked memories, for example, will
+        /// not access an invalid address but may instead load from NULL or may
+        /// explicitly jump to a `ud2` instruction. This is only available for
+        /// fault-based traps which are one of the main ways, but not the only
+        /// way, to run wasm.
+        faulting_addr: Option<usize>,
+    },
 
     /// A trap raised from a wasm libcall
     Wasm(wasmtime_environ::Trap),
@@ -174,7 +190,7 @@ impl TrapReason {
 
     /// Is this a JIT trap?
     pub fn is_jit(&self) -> bool {
-        matches!(self, TrapReason::Jit(_))
+        matches!(self, TrapReason::Jit { .. })
     }
 }
 
@@ -470,12 +486,16 @@ impl CallThreadState {
         self.jmp_buf.replace(ptr::null())
     }
 
-    fn set_jit_trap(&self, pc: *const u8, fp: usize) {
+    fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option<usize>) {
         let backtrace = self.capture_backtrace(Some((pc as usize, fp)));
         unsafe {
-            (*self.unwind.get())
-                .as_mut_ptr()
-                .write((UnwindReason::Trap(TrapReason::Jit(pc as usize)), backtrace));
+            (*self.unwind.get()).as_mut_ptr().write((
+                UnwindReason::Trap(TrapReason::Jit {
+                    pc: pc as usize,
+                    faulting_addr,
+                }),
+                backtrace,
+            ));
         }
     }
 
diff --git a/crates/runtime/src/traphandlers/macos.rs b/crates/runtime/src/traphandlers/macos.rs
index da2cdac91..092be819e 100644
--- a/crates/runtime/src/traphandlers/macos.rs
+++ b/crates/runtime/src/traphandlers/macos.rs
@@ -75,7 +75,18 @@ mod mach_addons {
         pub static NDR_record: NDR_record_t;
     }
 
-    #[repr(C)]
+    // Note that this is copied from Gecko at
+    //
+    // https://searchfox.org/mozilla-central/rev/ed93119be4818da1509bbcb7b28e245853eeedd5/js/src/wasm/WasmSignalHandlers.cpp#583-601
+    //
+    // which distinctly diverges from the actual version of this in the header
+    // files provided by macOS, notably in the `code` field which uses `i64`
+    // instead of `i32`.
+    //
+    // Also note the `packed(4)` here which forcibly decreases alignment to 4 to
+    // additionally match what mach expects (apparently, I wish I had a better
+    // reference for this).
+    #[repr(C, packed(4))]
     #[allow(dead_code)]
     #[derive(Copy, Clone, Debug)]
     pub struct __Request__exception_raise_t {
@@ -88,6 +99,11 @@ mod mach_addons {
         pub NDR: NDR_record_t,
         pub exception: exception_type_t,
         pub codeCnt: mach_msg_type_number_t,
+
+        // Note that this is a divergence from the C headers which use
+        // `integer_t` here for this field which is a `c_int`. That isn't
+        // actually reflecting reality apparently though because if `c_int` is
+        // used here then the structure is too small to receive a message.
         pub code: [i64; 2],
     }
 
@@ -172,6 +188,7 @@ pub unsafe fn platform_init() {
 // This is largely just copied from SpiderMonkey.
 #[repr(C)]
 #[allow(dead_code)]
+#[derive(Debug)]
 struct ExceptionRequest {
     body: __Request__exception_raise_t,
     trailer: mach_msg_trailer_t,
@@ -248,6 +265,16 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
         _ => return false,
     }
 
+    // For `EXC_BAD_ACCESS` the faulting address is listed as the "subcode" in
+    // 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)
+    } else {
+        (0, 0)
+    };
+
     // Depending on the current architecture various bits and pieces of this
     // will change. This is expected to get filled out for other macos
     // platforms as necessary.
@@ -279,7 +306,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rbp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
                 // 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
@@ -306,6 +333,8 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rip = unwind as u64;
                 state.__rdi = pc as u64;
                 state.__rsi = fp as u64;
+                state.__rdx = fault1 as u64;
+                state.__rcx = fault2 as u64;
             };
             let mut thread_state = ThreadState::new();
         } else if #[cfg(target_arch = "aarch64")] {
@@ -318,7 +347,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__fp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
                 // 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
@@ -329,6 +358,8 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 // it looks like a call to unwind.
                 state.__x[0] = pc as u64;
                 state.__x[1] = fp as u64;
+                state.__x[2] = fault1 as u64;
+                state.__x[3] = fault2 as u64;
                 state.__pc = unwind as u64;
             };
             let mut thread_state = mem::zeroed::<ThreadState>();
@@ -373,7 +404,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
     // 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);
+    resume(&mut thread_state, pc as usize, fp, fault1, fault2);
     let kret = thread_set_state(
         origin_thread,
         thread_state_flavor,
@@ -390,10 +421,20 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
 /// a native backtrace once we've switched back to the thread itself. After
 /// the backtrace is captured we can do the usual `longjmp` back to the source
 /// of the wasm code.
-unsafe extern "C" fn unwind(wasm_pc: *const u8, wasm_fp: usize) -> ! {
+unsafe extern "C" fn unwind(
+    wasm_pc: *const u8,
+    wasm_fp: usize,
+    has_faulting_addr: usize,
+    faulting_addr: usize,
+) -> ! {
     let jmp_buf = tls::with(|state| {
         let state = state.unwrap();
-        state.set_jit_trap(wasm_pc, wasm_fp);
+        let faulting_addr = if has_faulting_addr != 0 {
+            Some(faulting_addr)
+        } else {
+            None
+        };
+        state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr);
         state.jmp_buf.get()
     });
     debug_assert!(!jmp_buf.is_null());
diff --git a/crates/runtime/src/traphandlers/unix.rs b/crates/runtime/src/traphandlers/unix.rs
index 889867ba1..183773370 100644
--- a/crates/runtime/src/traphandlers/unix.rs
+++ b/crates/runtime/src/traphandlers/unix.rs
@@ -101,7 +101,11 @@ unsafe extern "C" fn trap_handler(
         if jmp_buf as usize == 1 {
             return true;
         }
-        info.set_jit_trap(pc, fp);
+        let faulting_addr = match signum {
+            libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
+            _ => None,
+        };
+        info.set_jit_trap(pc, fp, faulting_addr);
         // 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/traphandlers/windows.rs b/crates/runtime/src/traphandlers/windows.rs
index a9c1b4f04..5ad7295f5 100644
--- a/crates/runtime/src/traphandlers/windows.rs
+++ b/crates/runtime/src/traphandlers/windows.rs
@@ -62,13 +62,23 @@ unsafe extern "system" fn exception_handler(exception_info: *mut EXCEPTION_POINT
                 compile_error!("unsupported platform");
             }
         }
+        // For access violations the first element in `ExceptionInformation` is
+        // an indicator as to whether the fault was a read/write. The second
+        // element is the address of the inaccessible data causing this
+        // violation.
+        let faulting_addr = if record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION {
+            assert!(record.NumberParameters >= 2);
+            Some(record.ExceptionInformation[1])
+        } 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);
+            info.set_jit_trap(ip, fp, faulting_addr);
             wasmtime_longjmp(jmp_buf)
         }
     })
diff --git a/crates/wasmtime/src/memory.rs b/crates/wasmtime/src/memory.rs
index 7dbf8d2f3..c3d17784f 100644
--- a/crates/wasmtime/src/memory.rs
+++ b/crates/wasmtime/src/memory.rs
@@ -5,6 +5,7 @@ use crate::{AsContext, AsContextMut, Engine, MemoryType, StoreContext, StoreCont
 use anyhow::{bail, Result};
 use std::cell::UnsafeCell;
 use std::convert::TryFrom;
+use std::ops::Range;
 use std::slice;
 use std::time::Instant;
 use wasmtime_environ::MemoryPlan;
@@ -611,6 +612,10 @@ pub unsafe trait LinearMemory: Send + Sync + 'static {
 
     /// Return the allocated memory as a mutable pointer to u8.
     fn as_ptr(&self) -> *mut u8;
+
+    /// Returns the range of native addresses that WebAssembly can natively
+    /// access from this linear memory, including guard pages.
+    fn wasm_accessible(&self) -> Range<usize>;
 }
 
 /// A memory creator. Can be used to provide a memory creator
diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs
index 866aa39f1..9a322fd6a 100644
--- a/crates/wasmtime/src/store.rs
+++ b/crates/wasmtime/src/store.rs
@@ -97,7 +97,7 @@ use wasmtime_runtime::{
     InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
     OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedFuncRef, VMContext,
     VMExternRef, VMExternRefActivationsTable, VMRuntimeLimits, VMSharedSignatureIndex,
-    VMTrampoline,
+    VMTrampoline, WasmFault,
 };
 
 mod context;
@@ -1501,6 +1501,68 @@ impl StoreOpaque {
     pub(crate) fn push_rooted_funcs(&mut self, funcs: Arc<[Definition]>) {
         self.rooted_host_funcs.push(funcs);
     }
+
+    /// Translates a WebAssembly fault at the native `pc` and native `addr` to a
+    /// WebAssembly-relative fault.
+    ///
+    /// This function may abort the process if `addr` is not found to actually
+    /// reside in any linear memory. In such a situation it means that the
+    /// segfault was erroneously caught by Wasmtime and is possibly indicative
+    /// of a code generator bug.
+    ///
+    /// This function returns `None` for dynamically-bounds-checked-memories
+    /// with spectre mitigations enabled since the hardware fault address is
+    /// 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 {
+            return None;
+        }
+
+        // Search all known instances in this store for this address. Note that
+        // this is probably not the speediest way to do this. Traps, however,
+        // are generally not expected to be super fast and additionally stores
+        // probably don't have all that many instances or memories.
+        //
+        // If this loop becomes hot in the future, however, it should be
+        // possible to precompute maps about linear memories in a store and have
+        // a quicker lookup.
+        let mut fault = None;
+        for instance in self.instances.iter() {
+            if let Some(f) = instance.handle.wasm_fault(addr) {
+                assert!(fault.is_none());
+                fault = Some(f);
+            }
+        }
+        if fault.is_some() {
+            return fault;
+        }
+
+        eprintln!(
+            "\
+Wasmtime caught a segfault for a wasm program because the faulting instruction
+is allowed to segfault due to how linear memories are implemented. The address
+that was accessed, however, is not known to any linear memory in use within this
+Store. This may be indicative of a critical bug in Wasmtime's code generation
+because all addresses which are known to be reachable from wasm won't reach this
+message.
+
+    pc:      0x{pc:x}
+    address: 0x{addr:x}
+
+This is a possible security issue because WebAssembly has accessed something it
+shouldn't have been able to. Other accesses may have succeeded and this one just
+happened to be caught. The process will now be aborted to prevent this damage
+from going any further and to alert what's going on. If this is a security
+issue please reach out to the Wasmtime team via its security policy
+at https://bytecodealliance.org/security.
+"
+        );
+        std::process::abort();
+    }
 }
 
 impl<T> StoreContextMut<'_, T> {
diff --git a/crates/wasmtime/src/trampoline/memory.rs b/crates/wasmtime/src/trampoline/memory.rs
index b0de46a90..e10e26bcc 100644
--- a/crates/wasmtime/src/trampoline/memory.rs
+++ b/crates/wasmtime/src/trampoline/memory.rs
@@ -4,6 +4,7 @@ use crate::store::{InstanceId, StoreOpaque};
 use crate::MemoryType;
 use anyhow::{anyhow, Result};
 use std::convert::TryFrom;
+use std::ops::Range;
 use std::sync::Arc;
 use wasmtime_environ::{
     DefinedMemoryIndex, DefinedTableIndex, EntityIndex, MemoryPlan, MemoryStyle, Module,
@@ -99,6 +100,10 @@ impl RuntimeLinearMemory for LinearMemoryProxy {
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }
+
+    fn wasm_accessible(&self) -> Range<usize> {
+        self.mem.wasm_accessible()
+    }
 }
 
 #[derive(Clone)]
diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs
index e1e3fdd9a..80d367d8a 100644
--- a/crates/wasmtime/src/trap.rs
+++ b/crates/wasmtime/src/trap.rs
@@ -103,12 +103,21 @@ pub(crate) fn from_runtime_box(
             );
             (error, None)
         }
-        wasmtime_runtime::TrapReason::Jit(pc) => {
+        wasmtime_runtime::TrapReason::Jit { pc, faulting_addr } => {
             let code = store
                 .modules()
                 .lookup_trap_code(pc)
                 .unwrap_or(Trap::StackOverflow);
-            (code.into(), Some(pc))
+            let mut err: Error = code.into();
+
+            // If a fault address was present, for example with segfaults,
+            // then simultaneously assert that it's within a known linear memory
+            // and additionally translate it to a wasm-local address to be added
+            // as context to the error.
+            if let Some(fault) = faulting_addr.and_then(|addr| store.wasm_fault(pc, addr)) {
+                err = err.context(fault);
+            }
+            (err, Some(pc))
         }
         wasmtime_runtime::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
     };


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/algebraic.clif b/cranelift/filetests/filetests/egraph/algebraic.clif
index 02d38cdd9..faae822fe 100644
--- a/cranelift/filetests/filetests/egraph/algebraic.clif
+++ b/cranelift/filetests/filetests/egraph/algebraic.clif
@@ -232,6 +232,14 @@ block0(v0: i32):
     ; check: return v0
 }
 
+function %isub_self(i32) -> i32 {
+block0(v0: i32):
+    v1 = isub v0, v0
+    return v1
+    ; check: v2 = iconst.i32 0
+    ; check: return v2
+}
+
 function %or_and_y_with_not_y_i8(i8, i8) -> i8 {
 block0(v0: i8, v1: i8):
     v2 = band v0, v1
diff --git a/cranelift/filetests/filetests/isa/x64/uextend-elision.clif b/cranelift/filetests/filetests/isa/x64/uextend-elision.clif
index cc277ff60..76689c768 100644
--- a/cranelift/filetests/filetests/isa/x64/uextend-elision.clif
+++ b/cranelift/filetests/filetests/isa/x64/uextend-elision.clif
@@ -79,6 +79,32 @@ block0(v0: i8x16):
 ;   popq %rbp
 ;   retq
 
+function %extractlane_i8x16_i64(i8x16) -> i64 {
+block0(v0: i8x16):
+    v1 = extractlane v0, 1
+    v2 = uextend.i64 v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrb  $1, %xmm0, %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+; 
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrb $1, %xmm0, %eax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
 function %extractlane_i16x8_i32(i16x8) -> i32 {
 block0(v0: i16x8):
     v1 = extractlane v0, 1
@@ -105,3 +131,55 @@ block0(v0: i16x8):
 ;   popq %rbp
 ;   retq
 
+function %extractlane_i16x8_i64(i16x8) -> i64 {
+block0(v0: i16x8):
+    v1 = extractlane v0, 1
+    v2 = uextend.i64 v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrw  $1, %xmm0, %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+; 
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrw $1, %xmm0, %eax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %extractlane_i32x4_i64(i32x4) -> i64 {
+block0(v0: i32x4):
+    v1 = extractlane v0, 1
+    v2 = uextend.i64 v1
+    return v2
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrd  $1, %xmm0, %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+; 
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrd $1, %xmm0, %eax
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/tests/all/memory_creator.rs b/tests/all/memory_creator.rs
index 01cc0fabf..26950ca61 100644
--- a/tests/all/memory_creator.rs
+++ b/tests/all/memory_creator.rs
@@ -6,6 +6,7 @@ mod not_for_windows {
     use rustix::mm::{mmap_anonymous, mprotect, munmap, MapFlags, MprotectFlags, ProtFlags};
 
     use std::convert::TryFrom;
+    use std::ops::Range;
     use std::ptr::null_mut;
     use std::sync::{Arc, Mutex};
 
@@ -74,6 +75,12 @@ mod not_for_windows {
         fn as_ptr(&self) -> *mut u8 {
             self.mem as *mut u8
         }
+
+        fn wasm_accessible(&self) -> Range<usize> {
+            let base = self.mem as usize;
+            let end = base + self.size;
+            base..end
+        }
     }
 
     struct CustomMemoryCreator {
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index ccceb7116..e6785979a 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -1279,3 +1279,44 @@ fn div_plus_load_reported_right() -> Result<()> {
         }
     }
 }
+
+#[test]
+fn wasm_fault_address_reported_by_default() -> Result<()> {
+    let engine = Engine::default();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (memory 1)
+                (func $start
+                    i32.const 0xdeadbeef
+                    i32.load
+                    drop)
+                (start $start)
+            )
+        "#,
+    )?;
+    let err = Instance::new(&mut store, &module, &[]).unwrap_err();
+
+    // On s390x faulting addressess are rounded to the nearest page boundary
+    // instead of having the precise address reported.
+    let mut expected_addr = 0xdeadbeef_u32;
+    if cfg!(target_arch = "s390x") {
+        expected_addr &= 0xfffff000;
+    }
+
+    // NB: at this time there's no programmatic access to the fault address
+    // because it's not always available for load/store traps. Only static
+    // memories on 32-bit have this information, but bounds-checked memories
+    // use manual trapping instructions and otherwise don't have a means of
+    // communicating the faulting address at this time.
+    let err = format!("{err:?}");
+    assert!(
+        err.contains(&format!(
+            "memory fault at wasm address 0x{expected_addr:x} in linear memory of size 0x10000"
+        )),
+        "bad error: {err}"
+    );
+    Ok(())
+}