Repository: bytecodealliance/wasmtime
Pull request #7507 (2023-11-08 22:12:34)

Remove workaround of upstream libc issue
The referenced PR has long since landed and been published, so remove the FIXME.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/cranelift/codegen/src/isa/aarch64/lower/isle.rs b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
index d3fa587be..a70e895fb 100644
--- a/cranelift/codegen/src/isa/aarch64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
@@ -13,12 +13,12 @@ use super::{
     MachLabel, MemLabel, MoveWideConst, MoveWideOp, Opcode, OperandSize, Reg, SImm9, ScalarSize,
     ShiftOpAndAmt, UImm12Scaled, UImm5, VecMisc2, VectorSize, NZCV,
 };
-use crate::ir::condcodes;
+use crate::ir::{condcodes, ArgumentExtension};
 use crate::isa;
 use crate::isa::aarch64::inst::{FPULeftShiftImm, FPURightShiftImm, ReturnCallInfo};
 use crate::isa::aarch64::AArch64Backend;
+use crate::machinst::isle::*;
 use crate::machinst::valueregs;
-use crate::machinst::{isle::*, InputSourceInst};
 use crate::{
     binemit::CodeOffset,
     ir::{
@@ -28,7 +28,6 @@ use crate::{
     isa::aarch64::abi::AArch64CallSite,
     isa::aarch64::inst::args::{ShiftOp, ShiftOpShiftImm},
     isa::aarch64::inst::SImm7Scaled,
-    isa::unwind::UnwindInst,
     machinst::{
         abi::ArgPair, ty_bits, InstOutput, Lower, MachInst, VCodeConstant, VCodeConstantData,
     },
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 875b53561..86b2a4ffa 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -5,7 +5,7 @@ use crate::binemit::{Addend, CodeOffset, Reloc};
 pub use crate::ir::condcodes::IntCC;
 use crate::ir::types::{self, F32, F64, I128, I16, I32, I64, I8, I8X16, R32, R64};
 
-pub use crate::ir::{ExternalName, MemFlags, Opcode, SourceLoc, Type, ValueLabel};
+pub use crate::ir::{ExternalName, MemFlags, Opcode, Type};
 use crate::isa::{CallConv, FunctionAlignment};
 use crate::machinst::*;
 use crate::{settings, CodegenError, CodegenResult};
diff --git a/cranelift/codegen/src/isa/s390x/lower/isle.rs b/cranelift/codegen/src/isa/s390x/lower/isle.rs
index cec975d06..6c035f159 100644
--- a/cranelift/codegen/src/isa/s390x/lower/isle.rs
+++ b/cranelift/codegen/src/isa/s390x/lower/isle.rs
@@ -16,10 +16,10 @@ use crate::machinst::isle::*;
 use crate::machinst::{MachLabel, Reg};
 use crate::{
     ir::{
-        condcodes::*, immediates::*, types::*, ArgumentPurpose, AtomicRmwOp, BlockCall, Endianness,
-        Inst, InstructionData, KnownSymbol, LibCall, MemFlags, Opcode, TrapCode, Value, ValueList,
+        condcodes::*, immediates::*, types::*, ArgumentExtension, ArgumentPurpose, AtomicRmwOp,
+        BlockCall, Endianness, Inst, InstructionData, KnownSymbol, LibCall, MemFlags, Opcode,
+        TrapCode, Value, ValueList,
     },
-    isa::unwind::UnwindInst,
     isa::CallConv,
     machinst::abi::ABIMachineSpec,
     machinst::{
diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs
index 5908ad58a..474484c71 100644
--- a/cranelift/codegen/src/isa/x64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/x64/lower/isle.rs
@@ -3,10 +3,7 @@
 // Pull in the ISLE generated code.
 pub(crate) mod generated_code;
 use crate::{
-    ir::types,
-    ir::AtomicRmwOp,
-    isa, isle_common_prelude_methods, isle_lower_prelude_methods,
-    machinst::{InputSourceInst, Reg, Writable},
+    ir::types, ir::AtomicRmwOp, isa, isle_common_prelude_methods, isle_lower_prelude_methods,
 };
 use generated_code::{Context, MInst, RegisterClass};
 
@@ -22,12 +19,9 @@ use crate::{
         types::*,
         BlockCall, Inst, InstructionData, MemFlags, Opcode, TrapCode, Value, ValueList,
     },
-    isa::{
-        unwind::UnwindInst,
-        x64::{
-            abi::X64CallSite,
-            inst::{args::*, regs, CallInfo, ReturnCallInfo},
-        },
+    isa::x64::{
+        abi::X64CallSite,
+        inst::{args::*, regs, CallInfo, ReturnCallInfo},
     },
     machinst::{
         isle::*, valueregs, ArgPair, InsnInput, InstOutput, Lower, MachAtomicRmwOp, MachInst,
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index 1fb41f9c7..7add8208e 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -7,16 +7,15 @@ use std::cell::Cell;
 pub use super::MachLabel;
 use super::RetPair;
 pub use crate::ir::{
-    condcodes, condcodes::CondCode, dynamic_to_fixed, ArgumentExtension, ArgumentPurpose, Constant,
-    DynamicStackSlot, ExternalName, FuncRef, GlobalValue, Immediate, SigRef, StackSlot,
+    condcodes, condcodes::CondCode, dynamic_to_fixed, Constant, DynamicStackSlot, ExternalName,
+    FuncRef, GlobalValue, Immediate, SigRef, StackSlot,
 };
-pub use crate::isa::unwind::UnwindInst;
-pub use crate::isa::TargetIsa;
+pub use crate::isa::{unwind::UnwindInst, TargetIsa};
 pub use crate::machinst::{
     ABIArg, ABIArgSlot, InputSourceInst, Lower, LowerBackend, RealReg, Reg, RelocDistance, Sig,
     VCodeInst, Writable,
 };
-pub use crate::settings::{OptLevel, TlsModel};
+pub use crate::settings::TlsModel;
 
 pub type Unit = ();
 pub type ValueSlice = (ValueList, usize);
diff --git a/cranelift/codegen/src/opts.rs b/cranelift/codegen/src/opts.rs
index 3a78cac5c..00f350c6e 100644
--- a/cranelift/codegen/src/opts.rs
+++ b/cranelift/codegen/src/opts.rs
@@ -4,12 +4,11 @@ use crate::egraph::{NewOrExistingInst, OptimizeCtx};
 use crate::ir::condcodes;
 pub use crate::ir::condcodes::{FloatCC, IntCC};
 use crate::ir::dfg::ValueDef;
-pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64, Uimm8, V128Imm};
+pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm8, V128Imm};
 pub use crate::ir::types::*;
 pub use crate::ir::{
-    dynamic_to_fixed, AtomicRmwOp, Block, BlockCall, Constant, DataFlowGraph, DynamicStackSlot,
-    FuncRef, GlobalValue, Immediate, InstructionData, JumpTable, MemFlags, Opcode, StackSlot,
-    Table, TrapCode, Type, Value,
+    dynamic_to_fixed, AtomicRmwOp, BlockCall, Constant, DynamicStackSlot, FuncRef, GlobalValue,
+    Immediate, InstructionData, MemFlags, Opcode, StackSlot, Table, TrapCode, Type, Value,
 };
 use crate::isle_common_prelude_methods;
 use crate::machinst::isle::*;
diff --git a/crates/cranelift/src/debug.rs b/crates/cranelift/src/debug.rs
index 04095d640..1b10eb351 100644
--- a/crates/cranelift/src/debug.rs
+++ b/crates/cranelift/src/debug.rs
@@ -11,7 +11,7 @@ pub enum ModuleMemoryOffset {
     Imported(u32),
 }
 
-pub use write_debuginfo::{emit_dwarf, DwarfSection, DwarfSectionRelocTarget};
+pub use write_debuginfo::{emit_dwarf, DwarfSectionRelocTarget};
 
 mod gc;
 mod transform;
diff --git a/crates/runtime/src/traphandlers/unix.rs b/crates/runtime/src/traphandlers/unix.rs
index 613119af7..760295d70 100644
--- a/crates/runtime/src/traphandlers/unix.rs
+++ b/crates/runtime/src/traphandlers/unix.rs
@@ -173,21 +173,6 @@ unsafe extern "C" fn trap_handler(
     }
 }
 
-// FIXME(rust-lang/libc#3312) the currenty definition of `ucontext_t` in the
-// `libc` crate is incorrect on aarch64-apple-drawin so it's defined here with a
-// more accurate definition. When that PR and/or issue is resolved then this
-// should no longer be necessary.
-#[repr(C)]
-#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
-struct ucontext_t {
-    uc_onstack: libc::c_int,
-    uc_sigmask: libc::sigset_t,
-    uc_stack: libc::stack_t,
-    uc_link: *mut libc::ucontext_t,
-    uc_mcsize: usize,
-    uc_mcontext: libc::mcontext_t,
-}
-
 unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const u8, usize) {
     cfg_if::cfg_if! {
         if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "x86_64"))] {
@@ -229,7 +214,7 @@ unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const
                 (*cx.uc_mcontext).__ss.__rbp as usize,
             )
         } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
-            let cx = &*(cx as *const ucontext_t);
+            let cx = &*(cx as *const libc::ucontext_t);
             (
                 (*cx.uc_mcontext).__ss.__pc as *const u8,
                 (*cx.uc_mcontext).__ss.__fp as usize,
@@ -285,7 +270,7 @@ unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
                 (*cx.uc_mcontext).__ss.__rsp -= 8;
             }
         } else if #[cfg(target_arch = "aarch64")] {
-            let cx = &mut *(cx as *mut ucontext_t);
+            let cx = &mut *(cx as *mut libc::ucontext_t);
             (*cx.uc_mcontext).__ss.__pc = pc as u64;
             (*cx.uc_mcontext).__ss.__x[0] = arg1 as u64;
         } else {


Test patch:
diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs
deleted file mode 100644
index fdfc22a7f..000000000
--- a/tests/all/custom_signal_handler.rs
+++ /dev/null
@@ -1,336 +0,0 @@
-#[cfg(any(
-    target_os = "linux",
-    all(target_os = "macos", feature = "posix-signals-on-macos")
-))]
-#[cfg(not(miri))]
-mod tests {
-    use anyhow::Result;
-    use rustix::mm::{mprotect, MprotectFlags};
-    use std::sync::atomic::{AtomicBool, Ordering};
-    use std::sync::Arc;
-    use wasmtime::unix::StoreExt;
-    use wasmtime::*;
-
-    const WAT1: &str = r#"
-(module
-  (func $hostcall_read (import "" "hostcall_read") (result i32))
-  (func $read (export "read") (result i32)
-    (i32.load (i32.const 0))
-  )
-  (func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
-    (i32.load
-      (i32.mul
-        ;; memory size in Wasm pages
-        (memory.size)
-        ;; Wasm page size
-        (i32.const 65536)
-      )
-    )
-  )
-  (func (export "hostcall_read") (result i32)
-    call $hostcall_read
-  )
-  (func $start
-    (i32.store (i32.const 0) (i32.const 123))
-  )
-  (start $start)
-  (memory (export "memory") 1 4)
-)
-"#;
-
-    const WAT2: &str = r#"
-(module
-  (import "other_module" "read" (func $other_module.read (result i32)))
-  (func $run (export "run") (result i32)
-      call $other_module.read)
-)
-"#;
-
-    fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
-        let ret = instance
-            .get_typed_func::<(), i32>(&mut *store, func_name)?
-            .call(store, ())?;
-        Ok(ret)
-    }
-
-    // Locate "memory" export, get base address and size and set memory protection to PROT_NONE
-    fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
-        let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
-        let base = mem_export.data_ptr(&store);
-        let length = mem_export.data_size(&store);
-
-        // So we can later trigger SIGSEGV by performing a read
-        unsafe {
-            mprotect(
-                base as *mut std::ffi::c_void,
-                length,
-                MprotectFlags::empty(),
-            )
-            .unwrap();
-        }
-
-        println!("memory: base={:?}, length={}", base, length);
-
-        (base as usize, length)
-    }
-
-    fn handle_sigsegv(
-        base: usize,
-        length: usize,
-        signum: libc::c_int,
-        siginfo: *const libc::siginfo_t,
-    ) -> bool {
-        println!("Hello from instance signal handler!");
-        // SIGSEGV on Linux, SIGBUS on Mac
-        if libc::SIGSEGV == signum || libc::SIGBUS == signum {
-            let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
-            // Any signal from within module's memory we handle ourselves
-            let result = (si_addr as u64) < (base as u64) + (length as u64);
-            // Remove protections so the execution may resume
-            unsafe {
-                mprotect(
-                    base as *mut libc::c_void,
-                    length,
-                    MprotectFlags::READ | MprotectFlags::WRITE,
-                )
-                .unwrap();
-            }
-            println!("signal handled: {}", result);
-            result
-        } else {
-            // Otherwise, we forward to wasmtime's signal handler.
-            false
-        }
-    }
-
-    fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
-        module
-            .imports()
-            .map(|import| {
-                assert_eq!("hostcall_read", import.name());
-                let func = Func::wrap(&mut *store, {
-                    move |mut caller: Caller<'_, _>| {
-                        let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
-                        let memory = mem.data(&caller);
-                        i32::from_le_bytes(memory[0..4].try_into().unwrap())
-                    }
-                });
-                wasmtime::Extern::Func(func)
-            })
-            .collect::<Vec<_>>()
-    }
-
-    // This test will only succeed if the SIGSEGV signal originating from the
-    // hostcall can be handled.
-    #[test]
-    fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        let externs = make_externs(&mut store, &module);
-        let instance = Instance::new(&mut store, &module, &externs)?;
-
-        let (base, length) = set_up_memory(&mut store, instance);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base, length, signum, siginfo)
-            });
-        }
-        println!("calling hostcall_read...");
-        let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
-        assert_eq!(123, result);
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_single_instance() -> Result<()> {
-        let engine = Engine::new(&Config::default())?;
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        let externs = make_externs(&mut store, &module);
-        let instance = Instance::new(&mut store, &module, &externs)?;
-
-        let (base, length) = set_up_memory(&mut store, instance);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base, length, signum, siginfo)
-            });
-        }
-
-        // these invoke wasmtime_call_trampoline from action.rs
-        {
-            println!("calling read...");
-            let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
-            assert_eq!(123, result);
-        }
-
-        {
-            println!("calling read_out_of_bounds...");
-            let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
-                .unwrap_err()
-                .downcast::<Trap>()?;
-            assert_eq!(trap, Trap::MemoryOutOfBounds);
-        }
-
-        // these invoke wasmtime_call_trampoline from callable.rs
-        {
-            let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
-            println!("calling read...");
-            let result = read_func
-                .call(&mut store, ())
-                .expect("expected function not to trap");
-            assert_eq!(123i32, result);
-        }
-
-        {
-            let read_out_of_bounds_func =
-                instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
-            println!("calling read_out_of_bounds...");
-            let trap = read_out_of_bounds_func
-                .call(&mut store, ())
-                .unwrap_err()
-                .downcast::<Trap>()?;
-            assert_eq!(trap, Trap::MemoryOutOfBounds);
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_multiple_instances() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        // Set up multiple instances
-
-        let externs = make_externs(&mut store, &module);
-        let instance1 = Instance::new(&mut store, &module, &externs)?;
-        let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
-
-        unsafe {
-            let (base1, length1) = set_up_memory(&mut store, instance1);
-
-            store.set_signal_handler({
-                let instance1_handler_triggered = instance1_handler_triggered.clone();
-                move |_signum, _siginfo, _context| {
-                    // Remove protections so the execution may resume
-                    mprotect(
-                        base1 as *mut libc::c_void,
-                        length1,
-                        MprotectFlags::READ | MprotectFlags::WRITE,
-                    )
-                    .unwrap();
-                    instance1_handler_triggered.store(true, Ordering::SeqCst);
-                    println!(
-                        "Hello from instance1 signal handler! {}",
-                        instance1_handler_triggered.load(Ordering::SeqCst)
-                    );
-                    true
-                }
-            });
-        }
-
-        // Invoke both instances and trigger both signal handlers
-
-        // First instance1
-        {
-            let mut exports1 = instance1.exports(&mut store);
-            assert!(exports1.next().is_some());
-            drop(exports1);
-
-            println!("calling instance1.read...");
-            let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
-            assert_eq!(123, result);
-            assert_eq!(
-                instance1_handler_triggered.load(Ordering::SeqCst),
-                true,
-                "instance1 signal handler has been triggered"
-            );
-        }
-
-        let externs = make_externs(&mut store, &module);
-        let instance2 =
-            Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
-        let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
-
-        unsafe {
-            let (base2, length2) = set_up_memory(&mut store, instance2);
-
-            store.set_signal_handler({
-                let instance2_handler_triggered = instance2_handler_triggered.clone();
-                move |_signum, _siginfo, _context| {
-                    // Remove protections so the execution may resume
-                    mprotect(
-                        base2 as *mut libc::c_void,
-                        length2,
-                        MprotectFlags::READ | MprotectFlags::WRITE,
-                    )
-                    .unwrap();
-                    instance2_handler_triggered.store(true, Ordering::SeqCst);
-                    println!(
-                        "Hello from instance2 signal handler! {}",
-                        instance2_handler_triggered.load(Ordering::SeqCst)
-                    );
-                    true
-                }
-            });
-        }
-
-        // And then instance2
-        {
-            let mut exports2 = instance2.exports(&mut store);
-            assert!(exports2.next().is_some());
-            drop(exports2);
-
-            println!("calling instance2.read...");
-            let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
-            assert_eq!(123, result);
-            assert_eq!(
-                instance2_handler_triggered.load(Ordering::SeqCst),
-                true,
-                "instance1 signal handler has been triggered"
-            );
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-
-        // instance1 which defines 'read'
-        let module1 = Module::new(&engine, WAT1)?;
-        let externs = make_externs(&mut store, &module1);
-        let instance1 = Instance::new(&mut store, &module1, &externs)?;
-        let (base1, length1) = set_up_memory(&mut store, instance1);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                println!("instance1");
-                handle_sigsegv(base1, length1, signum, siginfo)
-            });
-        }
-
-        let mut instance1_exports = instance1.exports(&mut store);
-        let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
-        drop(instance1_exports);
-
-        // instance2 which calls 'instance1.read'
-        let module2 = Module::new(&engine, WAT2)?;
-        let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
-        // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
-        // SIGSEGV originating from within the memory of instance1
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base1, length1, signum, siginfo)
-            });
-        }
-
-        println!("calling instance2.run");
-        let result = invoke_export(&mut store, instance2, "run")?;
-        assert_eq!(123, result);
-        Ok(())
-    }
-}
diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs
index 6686daf5c..7f04a541e 100644
--- a/tests/all/fuel.rs
+++ b/tests/all/fuel.rs
@@ -220,3 +220,40 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() {
     let consumed_fuel = init_fuel - store.get_fuel().unwrap();
     assert!(consumed_fuel > 0);
 }
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn get_fuel_clamps_at_zero() -> Result<()> {
+    let engine = Engine::new(Config::new().consume_fuel(true))?;
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+(module
+  (func $add2 (export "add2") (param $n i32) (result i32)
+    (i32.add (local.get $n) (i32.const 2))
+  )
+)
+        "#,
+    )?;
+    let instance = Instance::new(&mut store, &module, &[])?;
+
+    let add2 = instance.get_typed_func::<i32, i32>(&mut store, "add2")?;
+
+    // Start with 6 fuel and one invocation of this function should cost 4 fuel
+    store.set_fuel(6)?;
+    assert_eq!(store.get_fuel()?, 6);
+    add2.call(&mut store, 10)?;
+    assert_eq!(store.get_fuel()?, 2);
+
+    // One more invocation of the function would technically take us to -2 fuel,
+    // but that's not representable, so the store should report 0 fuel after
+    // this completes.
+    add2.call(&mut store, 10)?;
+    assert_eq!(store.get_fuel()?, 0);
+
+    // Any further attempts should fail.
+    assert!(add2.call(&mut store, 10).is_err());
+
+    Ok(())
+}
diff --git a/tests/all/main.rs b/tests/all/main.rs
index 79ad24c60..cffe15555 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -6,7 +6,6 @@ mod cli_tests;
 mod code_too_large;
 mod component_model;
 mod coredump;
-mod custom_signal_handler;
 mod debug;
 mod epoch_interruption;
 mod externals;
diff --git a/tests/custom_signal_handler.rs b/tests/custom_signal_handler.rs
new file mode 100644
index 000000000..07a4a4ee0
--- /dev/null
+++ b/tests/custom_signal_handler.rs
@@ -0,0 +1,341 @@
+// NB: this is a separate test file because on macOS this should use signals
+// instead of Mach Ports and only one can be configured globally per process.
+
+#![cfg(any(target_os = "linux", target_os = "macos"))]
+#![cfg(not(miri))]
+
+use anyhow::Result;
+use rustix::mm::{mprotect, MprotectFlags};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use wasmtime::unix::StoreExt;
+use wasmtime::*;
+
+const WAT1: &str = r#"
+(module
+  (func $hostcall_read (import "" "hostcall_read") (result i32))
+  (func $read (export "read") (result i32)
+    (i32.load (i32.const 0))
+  )
+  (func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
+    (i32.load
+      (i32.mul
+        ;; memory size in Wasm pages
+        (memory.size)
+        ;; Wasm page size
+        (i32.const 65536)
+      )
+    )
+  )
+  (func (export "hostcall_read") (result i32)
+    call $hostcall_read
+  )
+  (func $start
+    (i32.store (i32.const 0) (i32.const 123))
+  )
+  (start $start)
+  (memory (export "memory") 1 4)
+)
+"#;
+
+const WAT2: &str = r#"
+(module
+  (import "other_module" "read" (func $other_module.read (result i32)))
+  (func $run (export "run") (result i32)
+      call $other_module.read)
+)
+"#;
+
+fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
+    let ret = instance
+        .get_typed_func::<(), i32>(&mut *store, func_name)?
+        .call(store, ())?;
+    Ok(ret)
+}
+
+// Locate "memory" export, get base address and size and set memory protection to PROT_NONE
+fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
+    let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
+    let base = mem_export.data_ptr(&store);
+    let length = mem_export.data_size(&store);
+
+    // So we can later trigger SIGSEGV by performing a read
+    unsafe {
+        mprotect(
+            base as *mut std::ffi::c_void,
+            length,
+            MprotectFlags::empty(),
+        )
+        .unwrap();
+    }
+
+    println!("memory: base={:?}, length={}", base, length);
+
+    (base as usize, length)
+}
+
+fn handle_sigsegv(
+    base: usize,
+    length: usize,
+    signum: libc::c_int,
+    siginfo: *const libc::siginfo_t,
+) -> bool {
+    println!("Hello from instance signal handler!");
+    // SIGSEGV on Linux, SIGBUS on Mac
+    if libc::SIGSEGV == signum || libc::SIGBUS == signum {
+        let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
+        // Any signal from within module's memory we handle ourselves
+        let result = (si_addr as u64) < (base as u64) + (length as u64);
+        // Remove protections so the execution may resume
+        unsafe {
+            mprotect(
+                base as *mut libc::c_void,
+                length,
+                MprotectFlags::READ | MprotectFlags::WRITE,
+            )
+            .unwrap();
+        }
+        println!("signal handled: {}", result);
+        result
+    } else {
+        // Otherwise, we forward to wasmtime's signal handler.
+        false
+    }
+}
+
+fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
+    module
+        .imports()
+        .map(|import| {
+            assert_eq!("hostcall_read", import.name());
+            let func = Func::wrap(&mut *store, {
+                move |mut caller: Caller<'_, _>| {
+                    let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
+                    let memory = mem.data(&caller);
+                    i32::from_le_bytes(memory[0..4].try_into().unwrap())
+                }
+            });
+            wasmtime::Extern::Func(func)
+        })
+        .collect::<Vec<_>>()
+}
+
+fn engine() -> Engine {
+    let mut config = Config::new();
+    config.macos_use_mach_ports(false);
+    Engine::new(&config).unwrap()
+}
+
+// This test will only succeed if the SIGSEGV signal originating from the
+// hostcall can be handled.
+#[test]
+fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    let externs = make_externs(&mut store, &module);
+    let instance = Instance::new(&mut store, &module, &externs)?;
+
+    let (base, length) = set_up_memory(&mut store, instance);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base, length, signum, siginfo)
+        });
+    }
+    println!("calling hostcall_read...");
+    let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
+    assert_eq!(123, result);
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_single_instance() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    let externs = make_externs(&mut store, &module);
+    let instance = Instance::new(&mut store, &module, &externs)?;
+
+    let (base, length) = set_up_memory(&mut store, instance);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base, length, signum, siginfo)
+        });
+    }
+
+    // these invoke wasmtime_call_trampoline from action.rs
+    {
+        println!("calling read...");
+        let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
+        assert_eq!(123, result);
+    }
+
+    {
+        println!("calling read_out_of_bounds...");
+        let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
+            .unwrap_err()
+            .downcast::<Trap>()?;
+        assert_eq!(trap, Trap::MemoryOutOfBounds);
+    }
+
+    // these invoke wasmtime_call_trampoline from callable.rs
+    {
+        let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
+        println!("calling read...");
+        let result = read_func
+            .call(&mut store, ())
+            .expect("expected function not to trap");
+        assert_eq!(123i32, result);
+    }
+
+    {
+        let read_out_of_bounds_func =
+            instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
+        println!("calling read_out_of_bounds...");
+        let trap = read_out_of_bounds_func
+            .call(&mut store, ())
+            .unwrap_err()
+            .downcast::<Trap>()?;
+        assert_eq!(trap, Trap::MemoryOutOfBounds);
+    }
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_multiple_instances() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    // Set up multiple instances
+
+    let externs = make_externs(&mut store, &module);
+    let instance1 = Instance::new(&mut store, &module, &externs)?;
+    let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
+
+    unsafe {
+        let (base1, length1) = set_up_memory(&mut store, instance1);
+
+        store.set_signal_handler({
+            let instance1_handler_triggered = instance1_handler_triggered.clone();
+            move |_signum, _siginfo, _context| {
+                // Remove protections so the execution may resume
+                mprotect(
+                    base1 as *mut libc::c_void,
+                    length1,
+                    MprotectFlags::READ | MprotectFlags::WRITE,
+                )
+                .unwrap();
+                instance1_handler_triggered.store(true, Ordering::SeqCst);
+                println!(
+                    "Hello from instance1 signal handler! {}",
+                    instance1_handler_triggered.load(Ordering::SeqCst)
+                );
+                true
+            }
+        });
+    }
+
+    // Invoke both instances and trigger both signal handlers
+
+    // First instance1
+    {
+        let mut exports1 = instance1.exports(&mut store);
+        assert!(exports1.next().is_some());
+        drop(exports1);
+
+        println!("calling instance1.read...");
+        let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
+        assert_eq!(123, result);
+        assert_eq!(
+            instance1_handler_triggered.load(Ordering::SeqCst),
+            true,
+            "instance1 signal handler has been triggered"
+        );
+    }
+
+    let externs = make_externs(&mut store, &module);
+    let instance2 =
+        Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
+    let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
+
+    unsafe {
+        let (base2, length2) = set_up_memory(&mut store, instance2);
+
+        store.set_signal_handler({
+            let instance2_handler_triggered = instance2_handler_triggered.clone();
+            move |_signum, _siginfo, _context| {
+                // Remove protections so the execution may resume
+                mprotect(
+                    base2 as *mut libc::c_void,
+                    length2,
+                    MprotectFlags::READ | MprotectFlags::WRITE,
+                )
+                .unwrap();
+                instance2_handler_triggered.store(true, Ordering::SeqCst);
+                println!(
+                    "Hello from instance2 signal handler! {}",
+                    instance2_handler_triggered.load(Ordering::SeqCst)
+                );
+                true
+            }
+        });
+    }
+
+    // And then instance2
+    {
+        let mut exports2 = instance2.exports(&mut store);
+        assert!(exports2.next().is_some());
+        drop(exports2);
+
+        println!("calling instance2.read...");
+        let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
+        assert_eq!(123, result);
+        assert_eq!(
+            instance2_handler_triggered.load(Ordering::SeqCst),
+            true,
+            "instance1 signal handler has been triggered"
+        );
+    }
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+
+    // instance1 which defines 'read'
+    let module1 = Module::new(&engine, WAT1)?;
+    let externs = make_externs(&mut store, &module1);
+    let instance1 = Instance::new(&mut store, &module1, &externs)?;
+    let (base1, length1) = set_up_memory(&mut store, instance1);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            println!("instance1");
+            handle_sigsegv(base1, length1, signum, siginfo)
+        });
+    }
+
+    let mut instance1_exports = instance1.exports(&mut store);
+    let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
+    drop(instance1_exports);
+
+    // instance2 which calls 'instance1.read'
+    let module2 = Module::new(&engine, WAT2)?;
+    let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
+    // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
+    // SIGSEGV originating from within the memory of instance1
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base1, length1, signum, siginfo)
+        });
+    }
+
+    println!("calling instance2.run");
+    let result = invoke_export(&mut store, instance2, "run")?;
+    assert_eq!(123, result);
+    Ok(())
+}