Repository: bytecodealliance/wasmtime
Pull request #5994 (2023-03-11 18:22:03)

fuzzgen: Add more arithmetic ops
👋 Hey,

This is part 2 of #5991. In that PR I added some arithmetic ops to the interpreter, in this one we add those to the fuzzer.

Some notes:
* SIMD `rotl`/`rotr`/`ishl`/`ushr`/`sshr` were affected by #5991 but since the RHS for these ops is scalar, the interpreter is returning the wrong result so I didn't add them here.
* `bnot` is a unary op, but was being inserted as a binary op. We were ignoring the second argument, I've now fixed that in the opcode list.

This PR is built on top of #5991, but the only relevant commit is the last one.

I've been running the fuzzer for about an hour on x86 and AArch64 and it hasn't complained yet, I'll update this if it finds something.


Patch:
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/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/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(())
+}