Repository: bytecodealliance/wasmtime
Pull request #8275 (2024-04-01 07:00:45)

cranelift: Fix indirect tail calls on aarch64/riscv64
x64 now uses a dedicated codegen strategy for tail-calls which writes the callee's stack arguments directly into their final location, but aarch64 and riscv64 still use our previous strategy of setting up for a normal function call and then moving the new stack frame up to overwrite the old one. In order to emit correct code for a normal function call, we need to fake a normal Call/CallIndirect opcode rather than ReturnCall/ReturnCallIndirect.

The new tests are a combination of x64's compile-tests for return-call-indirect.clif, plus a large-stack-frame test from aarch64/riscv64's return-call.clif modified to do an indirect call.

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

cc @cfallin, @fitzgen

<details>
This issue or pull request has been labeled: "cranelift", "cranelift:area:aarch64", "isle"

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

* cfallin: isle
* fitzgen: isle

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

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

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3ea4fbf8c..171f99d65 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -325,6 +325,12 @@ jobs:
     - run: cargo check -p wasmtime-c-api --no-default-features --features wat
     - run: cargo check -p wasmtime-c-api --no-default-features --features wasi
 
+    # Check that wasmtime-runtime compiles with panic=abort since there's some
+    # #[cfg] for specifically panic=abort there.
+    - run: cargo check -p wasmtime-runtime
+      env:
+        RUSTFLAGS: -Cpanic=abort
+
     # Check a few builds of the cranelift backend
     # - only x86 backend support,
     # - only arm64 backend support,
diff --git a/Cargo.lock b/Cargo.lock
index fc4bd14c2..177f28b64 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2327,17 +2327,16 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.16.20"
+version = "0.17.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
 dependencies = [
  "cc",
+ "getrandom",
  "libc",
- "once_cell",
- "spin 0.5.2",
+ "spin",
  "untrusted",
- "web-sys",
- "winapi",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2369,23 +2368,32 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.21.6"
+version = "0.22.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
+checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
 dependencies = [
  "log",
  "ring",
+ "rustls-pki-types",
  "rustls-webpki",
- "sct",
+ "subtle",
+ "zeroize",
 ]
 
+[[package]]
+name = "rustls-pki-types"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8"
+
 [[package]]
 name = "rustls-webpki"
-version = "0.101.4"
+version = "0.102.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
 dependencies = [
  "ring",
+ "rustls-pki-types",
  "untrusted",
 ]
 
@@ -2428,16 +2436,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
-[[package]]
-name = "sct"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
-dependencies = [
- "ring",
- "untrusted",
-]
-
 [[package]]
 name = "semver"
 version = "1.0.17"
@@ -2595,12 +2593,6 @@ dependencies = [
  "smallvec",
 ]
 
-[[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
 [[package]]
 name = "spin"
 version = "0.9.4"
@@ -2631,6 +2623,12 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
 [[package]]
 name = "symbolic_expressions"
 version = "5.0.3"
@@ -2855,11 +2853,12 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.24.0"
+version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
 dependencies = [
  "rustls",
+ "rustls-pki-types",
  "tokio",
 ]
 
@@ -3026,20 +3025,21 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
 
 [[package]]
 name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
 name = "ureq"
-version = "2.9.1"
+version = "2.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
+checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35"
 dependencies = [
  "base64",
  "log",
  "once_cell",
  "rustls",
+ "rustls-pki-types",
  "rustls-webpki",
  "url",
  "webpki-roots",
@@ -3334,7 +3334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acfc1e384a36ca532d070a315925887247f3c7e23567e23e0ac9b1c5d6b8bf76"
 dependencies = [
  "smallvec",
- "spin 0.9.4",
+ "spin",
  "wasmi_arena",
  "wasmi_core",
  "wasmparser-nostd",
@@ -4011,9 +4011,12 @@ dependencies = [
 
 [[package]]
 name = "webpki-roots"
-version = "0.25.2"
+version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
+checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
+dependencies = [
+ "rustls-pki-types",
+]
 
 [[package]]
 name = "which"
@@ -4470,6 +4473,12 @@ dependencies = [
  "syn 2.0.32",
 ]
 
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
 [[package]]
 name = "zstd"
 version = "0.13.0"
diff --git a/cranelift/codegen/src/isa/aarch64/lower/isle.rs b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
index b5e4ec51c..0f50361a7 100644
--- a/cranelift/codegen/src/isa/aarch64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
@@ -131,7 +131,9 @@ impl Context for IsleContext<'_, '_, MInst, AArch64Backend> {
             self.lower_ctx.sigs(),
             callee_sig,
             callee,
-            Opcode::ReturnCallIndirect,
+            // TODO: this should be Opcode::ReturnCallIndirect, once aarch64 has
+            // been ported to the new tail call strategy.
+            Opcode::CallIndirect,
             caller_conv,
             self.backend.flags().clone(),
         );
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index fe721122d..261a8f435 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -113,7 +113,9 @@ impl generated_code::Context for RV64IsleContext<'_, '_, MInst, Riscv64Backend>
             self.lower_ctx.sigs(),
             callee_sig,
             callee,
-            Opcode::ReturnCallIndirect,
+            // TODO: this should be Opcode::ReturnCallIndirect, once riscv64 has
+            // been ported to the new tail call strategy.
+            Opcode::CallIndirect,
             caller_conv,
             self.backend.flags().clone(),
         );
diff --git a/cranelift/object/src/backend.rs b/cranelift/object/src/backend.rs
index 3c37a5de5..581f6a87a 100644
--- a/cranelift/object/src/backend.rs
+++ b/cranelift/object/src/backend.rs
@@ -633,7 +633,6 @@ impl ObjectModule {
     }
 
     fn process_reloc(&self, record: &ModuleReloc) -> ObjectRelocRecord {
-        let mut addend = record.addend;
         let flags = match record.kind {
             Reloc::Abs4 => RelocationFlags::Generic {
                 kind: RelocationKind::Absolute,
@@ -693,7 +692,6 @@ impl ObjectModule {
                     object::BinaryFormat::MachO,
                     "MachOX86_64Tlv is not supported for this file format"
                 );
-                addend += 4; // X86_64_RELOC_TLV has an implicit addend of -4
                 RelocationFlags::MachO {
                     r_type: object::macho::X86_64_RELOC_TLV,
                     r_pcrel: true,
@@ -865,7 +863,7 @@ impl ObjectModule {
             offset: record.offset,
             name: record.name.clone(),
             flags,
-            addend,
+            addend: record.addend,
         }
     }
 }
diff --git a/crates/runtime/src/component/libcalls.rs b/crates/runtime/src/component/libcalls.rs
index cd0323ad3..ce3c00460 100644
--- a/crates/runtime/src/component/libcalls.rs
+++ b/crates/runtime/src/component/libcalls.rs
@@ -129,18 +129,17 @@ mod trampolines {
                     //
                     // Additionally assume that every function below returns a
                     // `Result` where errors turn into traps.
-                    let result = std::panic::catch_unwind(|| {
+                    let result = crate::traphandlers::catch_unwind_and_longjmp(|| {
                         shims!(@invoke $name() $($pname)*)
                     });
                     match result {
-                        Ok(Ok(ret)) => shims!(@convert_ret ret $($pname: $param)*),
-                        Ok(Err(err)) => crate::traphandlers::raise_trap(
+                        Ok(ret) => shims!(@convert_ret ret $($pname: $param)*),
+                        Err(err) => crate::traphandlers::raise_trap(
                             crate::traphandlers::TrapReason::User {
                                 error: err,
                                 needs_backtrace: true,
                             },
                         ),
-                        Err(panic) => crate::traphandlers::resume_panic(panic),
                     }
                 }
             )*
diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs
index 2d8677021..817748bde 100644
--- a/crates/runtime/src/libcalls.rs
+++ b/crates/runtime/src/libcalls.rs
@@ -111,17 +111,14 @@ pub mod raw {
                 ) $( -> libcall!(@ty $result))? {
                     $(#[cfg($attr)])?
                     {
-                        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+                        let ret = crate::traphandlers::catch_unwind_and_longjmp(|| {
                             Instance::from_vmctx(vmctx, |instance| {
                                 {
                                     super::$name(instance, $($pname),*)
                                 }
                             })
-                        }));
-                        match result {
-                            Ok(ret) => LibcallResult::convert(ret),
-                            Err(panic) => crate::traphandlers::resume_panic(panic),
-                        }
+                        });
+                        LibcallResult::convert(ret)
                     }
                     $(
                         #[cfg(not($attr))]
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 7f8ac5156..8acfef727 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -13,7 +13,6 @@ mod coredump;
 use crate::sys::traphandlers;
 use crate::{Instance, VMContext, VMRuntimeLimits};
 use anyhow::Error;
-use std::any::Any;
 use std::cell::{Cell, UnsafeCell};
 use std::mem::MaybeUninit;
 use std::ptr;
@@ -114,16 +113,37 @@ pub unsafe fn raise_lib_trap(trap: wasmtime_environ::Trap) -> ! {
     raise_trap(TrapReason::Wasm(trap))
 }
 
-/// Carries a Rust panic across wasm code and resumes the panic on the other
-/// side.
+/// Invokes the closure `f` and returns the result.
+///
+/// If `f` panics and this crate is compiled with `panic=unwind` this will
+/// catch the panic and capture it to "throw" with `longjmp` to be caught by
+/// the nearest `setjmp`. The panic will then be resumed from where it is
+/// caught.
 ///
 /// # Safety
 ///
 /// Only safe to call when wasm code is on the stack, aka `catch_traps` must
 /// have been previously called. Additionally no Rust destructors can be on the
-/// stack. They will be skipped and not executed.
-pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
-    tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
+/// stack. They will be skipped and not executed in the case that `f` panics.
+pub unsafe fn catch_unwind_and_longjmp<R>(f: impl FnOnce() -> R) -> R {
+    // With `panic=unwind` use `std::panic::catch_unwind` to catch possible
+    // panics to rethrow.
+    #[cfg(panic = "unwind")]
+    {
+        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
+            Ok(ret) => ret,
+            Err(err) => tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(err))),
+        }
+    }
+
+    // With `panic=abort` there's no use in using `std::panic::catch_unwind`
+    // since it won't actually catch anything. Note that
+    // `std::panic::catch_unwind` will technically optimize to this but having
+    // this branch avoids using the `std::panic` module entirely.
+    #[cfg(not(panic = "unwind"))]
+    {
+        f()
+    }
 }
 
 /// Stores trace message with backtrace.
@@ -259,6 +279,7 @@ where
             backtrace,
             coredumpstack,
         })),
+        #[cfg(panic = "unwind")]
         Err((UnwindReason::Panic(panic), _, _)) => std::panic::resume_unwind(panic),
     };
 
@@ -375,7 +396,8 @@ mod call_thread_state {
 pub use call_thread_state::*;
 
 enum UnwindReason {
-    Panic(Box<dyn Any + Send>),
+    #[cfg(panic = "unwind")]
+    Panic(Box<dyn std::any::Any + Send>),
     Trap(TrapReason),
 }
 
@@ -404,15 +426,19 @@ impl CallThreadState {
             // hypothetical backtrace to and it doesn't really make sense to try
             // in the first place since this is a Rust problem rather than a
             // Wasm problem.
-            UnwindReason::Panic(_)
+            #[cfg(panic = "unwind")]
+            UnwindReason::Panic(_) => (None, None),
             // And if we are just propagating an existing trap that already has
             // a backtrace attached to it, then there is no need to capture a
             // new backtrace either.
-            | UnwindReason::Trap(TrapReason::User {
+            UnwindReason::Trap(TrapReason::User {
                 needs_backtrace: false,
                 ..
             }) => (None, None),
-            UnwindReason::Trap(_) => (self.capture_backtrace(self.limits, None), self.capture_coredump(self.limits, None)),
+            UnwindReason::Trap(_) => (
+                self.capture_backtrace(self.limits, None),
+                self.capture_coredump(self.limits, None),
+            ),
         };
         unsafe {
             (*self.unwind.get())
diff --git a/crates/wasi-http/Cargo.toml b/crates/wasi-http/Cargo.toml
index cd9c097ce..9c9400ab9 100644
--- a/crates/wasi-http/Cargo.toml
+++ b/crates/wasi-http/Cargo.toml
@@ -30,9 +30,9 @@ wasmtime = { workspace = true, features = ['component-model'] }
 
 # The `ring` crate, used to implement TLS, does not build on riscv64 or s390x
 [target.'cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))'.dependencies]
-tokio-rustls = { version = "0.24.0" }
-rustls = { version = "0.21.6" }
-webpki-roots = { version = "0.25.2" }
+tokio-rustls = { version = "0.25.0" }
+rustls = { version = "0.22.0" }
+webpki-roots = { version = "0.26.0" }
 
 [dev-dependencies]
 test-programs-artifacts = { workspace = true }
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index 839af33f1..5c1b207e9 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -177,28 +177,27 @@ async fn handler(
 
         #[cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))]
         {
-            use tokio_rustls::rustls::OwnedTrustAnchor;
+            use rustls::pki_types::{ServerName, TrustAnchor};
 
             // derived from https://github.com/tokio-rs/tls/blob/master/tokio-rustls/examples/client/src/main.rs
             let mut root_cert_store = rustls::RootCertStore::empty();
-            root_cert_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
-                OwnedTrustAnchor::from_subject_spki_name_constraints(
-                    ta.subject,
-                    ta.spki,
-                    ta.name_constraints,
-                )
+            root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| TrustAnchor {
+                name_constraints: ta.name_constraints.to_owned(),
+                subject: ta.subject.to_owned(),
+                subject_public_key_info: ta.subject_public_key_info.to_owned(),
             }));
             let config = rustls::ClientConfig::builder()
-                .with_safe_defaults()
                 .with_root_certificates(root_cert_store)
                 .with_no_client_auth();
             let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
             let mut parts = authority.split(":");
             let host = parts.next().unwrap_or(&authority);
-            let domain = rustls::ServerName::try_from(host).map_err(|e| {
-                tracing::warn!("dns lookup error: {e:?}");
-                dns_error("invalid dns name".to_string(), 0)
-            })?;
+            let domain = ServerName::try_from(host)
+                .map_err(|e| {
+                    tracing::warn!("dns lookup error: {e:?}");
+                    dns_error("invalid dns name".to_string(), 0)
+                })?
+                .to_owned();
             let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
                 tracing::warn!("tls protocol error: {e:?}");
                 types::ErrorCode::TlsProtocolError
diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs
index ef352657b..a11477231 100644
--- a/crates/wasmtime/src/runtime/component/func/host.rs
+++ b/crates/wasmtime/src/runtime/component/func/host.rs
@@ -6,7 +6,6 @@ use crate::{AsContextMut, StoreContextMut, ValRaw};
 use anyhow::{bail, Context, Result};
 use std::any::Any;
 use std::mem::{self, MaybeUninit};
-use std::panic::{self, AssertUnwindSafe};
 use std::ptr::NonNull;
 use std::sync::Arc;
 use wasmtime_environ::component::{
@@ -291,10 +290,9 @@ fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<us
 }
 
 unsafe fn handle_result(func: impl FnOnce() -> Result<()>) {
-    match panic::catch_unwind(AssertUnwindSafe(func)) {
-        Ok(Ok(())) => {}
-        Ok(Err(e)) => crate::trap::raise(e),
-        Err(e) => wasmtime_runtime::resume_panic(e),
+    match wasmtime_runtime::catch_unwind_and_longjmp(func) {
+        Ok(()) => {}
+        Err(e) => crate::trap::raise(e),
     }
 }
 
diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs
index 4bdaa4506..00e91fdf4 100644
--- a/crates/wasmtime/src/runtime/func.rs
+++ b/crates/wasmtime/src/runtime/func.rs
@@ -9,7 +9,6 @@ use anyhow::{bail, Context as _, Error, Result};
 use std::ffi::c_void;
 use std::future::Future;
 use std::mem;
-use std::panic::{self, AssertUnwindSafe};
 use std::pin::Pin;
 use std::ptr::{self, NonNull};
 use std::sync::Arc;
@@ -2156,20 +2155,13 @@ macro_rules! impl_into_func {
                     $( $args: WasmTy, )*
                     R: WasmRet,
                 {
-                    enum CallResult<U> {
-                        Ok(U),
-                        Trap(anyhow::Error),
-                        Panic(Box<dyn std::any::Any + Send>),
-                    }
-
-                    // Note that this `result` is intentionally scoped into a
-                    // separate block. Handling traps and panics will involve
+                    // Note that this function is intentionally scoped into a
+                    // separate closure. Handling traps and panics will involve
                     // longjmp-ing from this function which means we won't run
                     // destructors. As a result anything requiring a destructor
-                    // should be part of this block, and the long-jmp-ing
-                    // happens after the block in handling `CallResult`.
-                    let caller_vmctx = VMContext::from_opaque(caller_vmctx);
-                    let result = Caller::with(caller_vmctx, |mut caller| {
+                    // should be part of this closure, and the long-jmp-ing
+                    // happens after the closure in handling the result.
+                    let run = move |mut caller: Caller<'_, T>| {
                         let vmctx = VMNativeCallHostFuncContext::from_opaque(vmctx);
                         let state = (*vmctx).host_state();
 
@@ -2180,59 +2172,45 @@ macro_rules! impl_into_func {
                         let state = &*(state as *const _ as *const HostFuncState<F>);
                         let func = &state.func;
 
-                        let ret = {
-                            panic::catch_unwind(AssertUnwindSafe(|| {
-                                if let Err(trap) = caller.store.0.call_hook(CallHook::CallingHost) {
-                                    return R::fallible_from_error(trap);
-                                }
-
-                                let mut store = AutoAssertNoGc::new(caller.store.0);
-                                $(let $args = $args::from_abi($args, &mut store);)*
-                                let _ = &mut store;
-                                drop(store);
-
-                                let r = func(
-                                    caller.sub_caller(),
-                                    $( $args, )*
-                                );
-                                if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) {
-                                    return R::fallible_from_error(trap);
-                                }
-                                r.into_fallible()
-                            }))
-                        };
-
-                        // Note that we need to be careful when dealing with traps
-                        // here. Traps are implemented with longjmp/setjmp meaning
-                        // that it's not unwinding and consequently no Rust
-                        // destructors are run. We need to be careful to ensure that
-                        // nothing on the stack needs a destructor when we exit
-                        // abnormally from this `match`, e.g. on `Err`, on
-                        // cross-store-issues, or if `Ok(Err)` is raised.
-                        match ret {
-                            Err(panic) => CallResult::Panic(panic),
-                            Ok(ret) => {
-                                // Because the wrapped function is not `unsafe`, we
-                                // can't assume it returned a value that is
-                                // compatible with this store.
-                                if !ret.compatible_with_store(caller.store.0) {
-                                    CallResult::Trap(anyhow::anyhow!("host function attempted to return cross-`Store` value to Wasm"))
-                                } else {
-                                    let mut store = AutoAssertNoGc::new(&mut **caller.store.0);
-                                    match ret.into_abi_for_ret(&mut store, retptr) {
-                                        Ok(val) => CallResult::Ok(val),
-                                        Err(trap) => CallResult::Trap(trap.into()),
-                                    }
-                                }
+                        let ret = 'ret: {
+                            if let Err(trap) = caller.store.0.call_hook(CallHook::CallingHost) {
+                                break 'ret R::fallible_from_error(trap);
+                            }
 
+                            let mut store = AutoAssertNoGc::new(caller.store.0);
+                            $(let $args = $args::from_abi($args, &mut store);)*
+                            let _ = &mut store;
+                            drop(store);
+
+                            let r = func(
+                                caller.sub_caller(),
+                                $( $args, )*
+                            );
+                            if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) {
+                                break 'ret R::fallible_from_error(trap);
                             }
+                            r.into_fallible()
+                        };
+
+                        if !ret.compatible_with_store(caller.store.0) {
+                            bail!("host function attempted to return cross-`Store` value to Wasm")
+                        } else {
+                            let mut store = AutoAssertNoGc::new(&mut **caller.store.0);
+                            let ret = ret.into_abi_for_ret(&mut store, retptr)?;
+                            Ok(ret)
                         }
+                    };
+
+                    // With nothing else on the stack move `run` into this
+                    // closure and then run it as part of `Caller::with`.
+                    let result = wasmtime_runtime::catch_unwind_and_longjmp(move || {
+                        let caller_vmctx = VMContext::from_opaque(caller_vmctx);
+                        Caller::with(caller_vmctx, run)
                     });
 
                     match result {
-                        CallResult::Ok(val) => val,
-                        CallResult::Trap(err) => crate::trap::raise(err),
-                        CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic),
+                        Ok(val) => val,
+                        Err(err) => crate::trap::raise(err),
                     }
                 }
 
diff --git a/crates/wasmtime/src/runtime/trampoline/func.rs b/crates/wasmtime/src/runtime/trampoline/func.rs
index 03aa90834..cfb0bb7ff 100644
--- a/crates/wasmtime/src/runtime/trampoline/func.rs
+++ b/crates/wasmtime/src/runtime/trampoline/func.rs
@@ -3,7 +3,6 @@
 use crate::type_registry::RegisteredType;
 use crate::{code_memory::CodeMemory, Engine, FuncType, ValRaw};
 use anyhow::Result;
-use std::panic::{self, AssertUnwindSafe};
 use std::ptr::NonNull;
 use wasmtime_runtime::{
     StoreBox, VMArrayCallHostFuncContext, VMContext, VMFuncRef, VMOpaqueContext,
@@ -47,7 +46,7 @@ unsafe extern "C" fn array_call_shim<F>(
     // below will trigger a longjmp, which won't run local destructors if we
     // have any. To prevent leaks we avoid having any local destructors by
     // avoiding local variables.
-    let result = panic::catch_unwind(AssertUnwindSafe(|| {
+    let result = wasmtime_runtime::catch_unwind_and_longjmp(|| {
         let vmctx = VMArrayCallHostFuncContext::from_opaque(vmctx);
         // Double-check ourselves in debug mode, but we control
         // the `Any` here so an unsafe downcast should also
@@ -57,22 +56,17 @@ unsafe extern "C" fn array_call_shim<F>(
         let state = &*(state as *const _ as *const TrampolineState<F>);
         let values_vec = std::slice::from_raw_parts_mut(values_vec, values_vec_len);
         (state.func)(VMContext::from_opaque(caller_vmctx), values_vec)
-    }));
+    });
 
     match result {
-        Ok(Ok(())) => {}
+        Ok(()) => {}
 
         // If a trap was raised (an error returned from the imported function)
         // then we smuggle the trap through `Box<dyn Error>` through to the
         // call-site, which gets unwrapped in `Trap::from_runtime` later on as we
         // convert from the internal `Trap` type to our own `Trap` type in this
         // crate.
-        Ok(Err(trap)) => crate::trap::raise(trap.into()),
-
-        // And finally if the imported function panicked, then we trigger the
-        // form of unwinding that's safe to jump over wasm code on all
-        // platforms.
-        Err(panic) => wasmtime_runtime::resume_panic(panic),
+        Err(trap) => crate::trap::raise(trap.into()),
     }
 }
 
diff --git a/crates/wiggle/src/borrow.rs b/crates/wiggle/src/borrow.rs
index d0a85288e..06b30db6b 100644
--- a/crates/wiggle/src/borrow.rs
+++ b/crates/wiggle/src/borrow.rs
@@ -1,259 +1,113 @@
 use crate::{BorrowHandle, GuestError, Region};
-use std::collections::HashMap;
-use std::sync::Mutex;
-
+use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
+
+/// A simple borrow checker to implement the API guarantees of Wiggle.
+///
+/// This is not a generalized borrow checker and is coarse-grained where it
+/// doesn't actually take any regions into account. Instead it only tracks
+/// whether there are temporally any shared/mutable borrows. This is
+/// more-or-less a poor-man's `RefCell<T>`.
+///
+/// Note that this uses `&AtomicU32` because this is passed around as
+/// `&BorrowChecker` in all `GuestPtr` structures. This needs to be mutated
+/// which might look like it needs `Cell<u32>`, but `&Cell<u32>` isn't `Sync`
+/// and we want futures with `&BorrowChecker` to be `Sync`, so this is an atomic
+/// instead. Only one of these is created per-invocation though and it's not
+/// actually shared across threads, so mutations here are not done with
+/// compare-and-swap but instead just loads/stores.
 pub struct BorrowChecker {
-    /// Unfortunately, since the terminology of std::cell and the problem domain of borrow checking
-    /// overlap, the method calls on this member will be confusing.
-    ///
-    /// Also, unfortunately, for now this uses a `Mutex`. The reason for that is
-    /// that this is shared as `&BorrowChecker` in a bunch of `GuestPtr` values.
-    /// Through this sharing we still want each `GuestPtr` to be `Send` and the
-    /// "naive" way to make `&T` `Send` with interior mutability is to use a
-    /// `Mutex`. Fixing this will likely require rethinking `GuestPtr` one way
-    /// or another. That needs to happen for other reasons as well (for example
-    /// to allow for wasm calls to happen while `GuestPtr` values are active),
-    /// so it's hoped that in a later rethinking of `GuestPtr` we can revisit
-    /// this and remove this `Mutex`.
-    bc: Mutex<InnerBorrowChecker>,
+    // 0        => no borrows
+    // >0       => shared borrows
+    // u32::MAX => mutable borrow
+    state: AtomicU32,
 }
 
 impl BorrowChecker {
-    /// A `BorrowChecker` manages run-time validation of borrows from a
-    /// `GuestMemory`. It keeps track of regions of guest memory which are
-    /// possible to alias with Rust references (via the `GuestSlice` and
-    /// `GuestStr` structs, which implement `std::ops::Deref` and
-    /// `std::ops::DerefMut`. It also enforces that `GuestPtr::read`
-    /// does not access memory with an outstanding mutable borrow, and
-    /// `GuestPtr::write` does not access memory with an outstanding
-    /// shared or mutable borrow.
     pub fn new() -> Self {
         BorrowChecker {
-            bc: Mutex::new(InnerBorrowChecker::new()),
+            state: AtomicU32::new(0),
         }
     }
-    /// Indicates whether any outstanding shared or mutable borrows are known
-    /// to the `BorrowChecker`. This function must be `false` in order for it
-    /// to be safe to recursively call into a WebAssembly module, or to
-    /// manipulate the WebAssembly memory by any other means.
-    pub fn has_outstanding_borrows(&self) -> bool {
-        self.bc.lock().unwrap().has_outstanding_borrows()
-    }
     pub fn shared_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
-        self.bc.lock().unwrap().shared_borrow(r)
+        match self.state.load(Relaxed) {
+            n if n >= u32::MAX - 1 => Err(GuestError::PtrBorrowed(r)),
+            n => {
+                self.state.store(n + 1, Relaxed);
+                Ok(BorrowHandle { _priv: () })
+            }
+        }
     }
     pub fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
-        self.bc.lock().unwrap().mut_borrow(r)
-    }
-    pub fn shared_unborrow(&self, h: BorrowHandle) {
-        self.bc.lock().unwrap().shared_unborrow(h)
-    }
-    pub fn mut_unborrow(&self, h: BorrowHandle) {
-        self.bc.lock().unwrap().mut_unborrow(h)
-    }
-    pub fn is_shared_borrowed(&self, r: Region) -> bool {
-        self.bc.lock().unwrap().is_shared_borrowed(r)
-    }
-    pub fn is_mut_borrowed(&self, r: Region) -> bool {
-        self.bc.lock().unwrap().is_mut_borrowed(r)
-    }
-}
-
-#[derive(Debug)]
-/// This is a pretty naive way to account for borrows. This datastructure
-/// could be made a lot more efficient with some effort.
-struct InnerBorrowChecker {
-    /// Maps from handle to region borrowed. A HashMap is probably not ideal
-    /// for this but it works. It would be more efficient if we could
-    /// check `is_borrowed` without an O(n) iteration, by organizing borrows
-    /// by an ordering of Region.
-    shared_borrows: HashMap<BorrowHandle, Region>,
-    mut_borrows: HashMap<BorrowHandle, Region>,
-    /// Handle to give out for the next borrow. This is the bare minimum of
-    /// bookkeeping of free handles, and in a pathological case we could run
-    /// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
-    next_handle: BorrowHandle,
-}
-
-impl InnerBorrowChecker {
-    fn new() -> Self {
-        InnerBorrowChecker {
-            shared_borrows: HashMap::new(),
-            mut_borrows: HashMap::new(),
-            next_handle: BorrowHandle(0),
+        match self.state.load(Relaxed) {
+            0 => {
+                self.state.store(u32::MAX, Relaxed);
+                Ok(BorrowHandle { _priv: () })
+            }
+            _ => Err(GuestError::PtrBorrowed(r)),
         }
     }
-
-    fn has_outstanding_borrows(&self) -> bool {
-        !(self.shared_borrows.is_empty() && self.mut_borrows.is_empty())
+    pub fn shared_unborrow(&self, _: BorrowHandle) {
+        let prev = self.state.load(Relaxed);
+        debug_assert!(prev > 0);
+        self.state.store(prev - 1, Relaxed);
     }
-
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        self.shared_borrows.values().any(|b| b.overlaps(r))
+    pub fn mut_unborrow(&self, _: BorrowHandle) {
+        let prev = self.state.load(Relaxed);
+        debug_assert!(prev == u32::MAX);
+        self.state.store(0, Relaxed);
     }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        self.mut_borrows.values().any(|b| b.overlaps(r))
-    }
-
-    fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
-        // Reset handles to 0 if all handles have been returned.
-        if self.shared_borrows.is_empty() && self.mut_borrows.is_empty() {
-            self.next_handle = BorrowHandle(0);
-        }
-        let h = self.next_handle;
-        // Get the next handle. Since we don't recycle handles until all of
-        // them have been returned, there is a pathological case where a user
-        // may make a Very Large (usize::MAX) number of valid borrows and
-        // unborrows while always keeping at least one borrow outstanding, and
-        // we will run out of borrow handles.
-        self.next_handle = BorrowHandle(
-            h.0.checked_add(1)
-                .ok_or_else(|| GuestError::BorrowCheckerOutOfHandles)?,
-        );
-        Ok(h)
+    pub fn can_read(&self, _: Region) -> bool {
+        self.state.load(Relaxed) != u32::MAX
     }
-
-    fn shared_borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
-        if self.is_mut_borrowed(r) {
-            return Err(GuestError::PtrBorrowed(r));
-        }
-        let h = self.new_handle()?;
-        self.shared_borrows.insert(h, r);
-        Ok(h)
-    }
-
-    fn mut_borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
-        if self.is_shared_borrowed(r) || self.is_mut_borrowed(r) {
-            return Err(GuestError::PtrBorrowed(r));
-        }
-        let h = self.new_handle()?;
-        self.mut_borrows.insert(h, r);
-        Ok(h)
-    }
-
-    fn shared_unborrow(&mut self, h: BorrowHandle) {
-        let removed = self.shared_borrows.remove(&h);
-        debug_assert!(removed.is_some(), "double-freed shared borrow");
-    }
-
-    fn mut_unborrow(&mut self, h: BorrowHandle) {
-        let removed = self.mut_borrows.remove(&h);
-        debug_assert!(removed.is_some(), "double-freed mut borrow");
+    pub fn can_write(&self, _: Region) -> bool {
+        self.state.load(Relaxed) == 0
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
-    #[test]
-    fn nonoverlapping() {
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(0, 10);
-        let r2 = Region::new(10, 10);
-        assert!(!r1.overlaps(r2));
-        bs.mut_borrow(r1).expect("can borrow r1");
-        bs.mut_borrow(r2).expect("can borrow r2");
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(10, 10);
-        let r2 = Region::new(0, 10);
-        assert!(!r1.overlaps(r2));
-        bs.mut_borrow(r1).expect("can borrow r1");
-        bs.mut_borrow(r2).expect("can borrow r2");
-    }
-
-    #[test]
-    fn overlapping() {
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(0, 10);
-        let r2 = Region::new(9, 10);
-        assert!(r1.overlaps(r2));
-        bs.shared_borrow(r1).expect("can borrow r1");
-        assert!(bs.mut_borrow(r2).is_err(), "cant mut borrow r2");
-        bs.shared_borrow(r2).expect("can shared borrow r2");
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(0, 10);
-        let r2 = Region::new(2, 5);
-        assert!(r1.overlaps(r2));
-        bs.shared_borrow(r1).expect("can borrow r1");
-        assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
-        bs.shared_borrow(r2).expect("can shared borrow r2");
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(9, 10);
-        let r2 = Region::new(0, 10);
-        assert!(r1.overlaps(r2));
-        bs.shared_borrow(r1).expect("can borrow r1");
-        assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
-        bs.shared_borrow(r2).expect("can shared borrow r2");
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(2, 5);
-        let r2 = Region::new(0, 10);
-        assert!(r1.overlaps(r2));
-        bs.shared_borrow(r1).expect("can borrow r1");
-        assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
-        bs.shared_borrow(r2).expect("can shared borrow r2");
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(2, 5);
-        let r2 = Region::new(10, 5);
-        let r3 = Region::new(15, 5);
-        let r4 = Region::new(0, 10);
-        assert!(r1.overlaps(r4));
-        bs.shared_borrow(r1).expect("can borrow r1");
-        bs.shared_borrow(r2).expect("can borrow r2");
-        bs.shared_borrow(r3).expect("can borrow r3");
-        assert!(bs.mut_borrow(r4).is_err(), "cant mut borrow r4");
-        bs.shared_borrow(r4).expect("can shared borrow r4");
-    }
 
     #[test]
-    fn unborrowing() {
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(0, 10);
-        let r2 = Region::new(10, 10);
-        assert!(!r1.overlaps(r2));
-        assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
-        let h1 = bs.mut_borrow(r1).expect("can borrow r1");
-        assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
-        let h2 = bs.mut_borrow(r2).expect("can borrow r2");
-
-        assert!(bs.mut_borrow(r2).is_err(), "can't borrow r2 twice");
-        bs.mut_unborrow(h2);
-        assert_eq!(
-            bs.has_outstanding_borrows(),
-            true,
-            "h1 is still outstanding"
-        );
-        bs.mut_unborrow(h1);
-        assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
-
-        let _h3 = bs
-            .mut_borrow(r2)
-            .expect("can borrow r2 again now that its been unborrowed");
-
-        // Lets try again with shared:
-
-        let mut bs = InnerBorrowChecker::new();
-        let r1 = Region::new(0, 10);
-        let r2 = Region::new(10, 10);
-        assert!(!r1.overlaps(r2));
-        assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
-        let h1 = bs.shared_borrow(r1).expect("can borrow r1");
-        assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
-        let h2 = bs.shared_borrow(r2).expect("can borrow r2");
-        let h3 = bs.shared_borrow(r2).expect("can shared borrow r2 twice");
-
-        bs.shared_unborrow(h2);
-        assert_eq!(
-            bs.has_outstanding_borrows(),
-            true,
-            "h1, h3 still outstanding"
-        );
-        bs.shared_unborrow(h1);
-        bs.shared_unborrow(h3);
-        assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
+    fn smoke() {
+        let b = BorrowChecker::new();
+        let mut next = 0;
+        let mut region = || {
+            let a = next;
+            next += 1;
+            Region::new(a, a + 1)
+        };
+
+        // can read/write initially
+        assert!(b.can_read(region()));
+        assert!(b.can_write(region()));
+
+        // can shared borrow multiple times which will prevent mutable borrows
+        let h1 = b.shared_borrow(region()).unwrap();
+        let h2 = b.shared_borrow(region()).unwrap();
+        assert!(b.mut_borrow(region()).is_err());
+
+        // can read, but can't write, while there are shared borrows
+        assert!(b.can_read(region()));
+        assert!(!b.can_write(region()));
+
+        // releasing shared borrows enables reading/writing again
+        b.shared_unborrow(h1);
+        b.shared_unborrow(h2);
+        assert!(b.can_read(region()));
+        assert!(b.can_write(region()));
+
+        // mutable borrow disallows shared borrows
+        let h1 = b.mut_borrow(region()).unwrap();
+        assert!(b.shared_borrow(region()).is_err());
+
+        // active mutable borrows disallows reads/writes
+        assert!(!b.can_read(region()));
+        assert!(!b.can_write(region()));
+
+        // releasing the mutable borrows allows raeding/writing again
+        b.mut_unborrow(h1);
+        assert!(b.can_read(region()));
+        assert!(b.can_write(region()));
     }
 }
diff --git a/crates/wiggle/src/guest_type.rs b/crates/wiggle/src/guest_type.rs
index 732f9e07c..52458b8cf 100644
--- a/crates/wiggle/src/guest_type.rs
+++ b/crates/wiggle/src/guest_type.rs
@@ -81,7 +81,7 @@ macro_rules! integer_primitives {
                 //
                 // Note that shared memories don't allow borrows and other
                 // shared borrows are ok to overlap with this.
-                if ptr.mem().is_mut_borrowed(region) {
+                if !ptr.mem().can_read(region) {
                     return Err(GuestError::PtrBorrowed(region));
                 }
 
@@ -106,7 +106,7 @@ macro_rules! integer_primitives {
                 let offset = ptr.offset();
                 let (host_ptr, region) = super::validate_size_align::<Self>(ptr.mem(), offset, 1)?;
                 let host_ptr = &host_ptr[0];
-                if ptr.mem().is_shared_borrowed(region) || ptr.mem().is_mut_borrowed(region) {
+                if !ptr.mem().can_write(region) {
                     return Err(GuestError::PtrBorrowed(region));
                 }
                 let atomic_value_ref: &$ty_atomic =
@@ -139,7 +139,7 @@ macro_rules! float_primitives {
                     1,
                 )?;
                 let host_ptr = &host_ptr[0];
-                if ptr.mem().is_mut_borrowed(region) {
+                if !ptr.mem().can_read(region) {
                     return Err(GuestError::PtrBorrowed(region));
                 }
                 let atomic_value_ref: &$ty_atomic =
@@ -158,7 +158,7 @@ macro_rules! float_primitives {
                     1,
                 )?;
                 let host_ptr = &host_ptr[0];
-                if ptr.mem().is_shared_borrowed(region) || ptr.mem().is_mut_borrowed(region) {
+                if !ptr.mem().can_write(region) {
                     return Err(GuestError::PtrBorrowed(region));
                 }
                 let atomic_value_ref: &$ty_atomic =
diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs
index 66fc3b49a..acdd99feb 100644
--- a/crates/wiggle/src/lib.rs
+++ b/crates/wiggle/src/lib.rs
@@ -72,8 +72,7 @@ pub mod wasmtime_crate {
 /// into the memory. The safety of this mechanism depends on there being exactly
 /// one associated tracking of borrows for a given WebAssembly memory. There
 /// must be no other reads or writes of WebAssembly the memory by either Rust or
-/// WebAssembly code while there are any outstanding borrows, as given by
-/// `GuestMemory::has_outstanding_borrows()`.
+/// WebAssembly code while there are any outstanding borrows.
 ///
 /// # Using References
 ///
@@ -83,12 +82,9 @@ pub mod wasmtime_crate {
 /// into the memory region given by a `GuestMemory`.
 ///
 /// These smart pointers are dynamically borrow-checked by the borrow checker
-/// methods on this trait. While a `GuestSlice` or a `GuestStr` are live, the
-/// [`GuestMemory::has_outstanding_borrows()`] method will always return `true`.
-/// If you need to re-enter the guest or otherwise read or write to the contents
-/// of a WebAssembly memory, all `GuestSlice`s and `GuestStr`s for the memory
-/// must be dropped, at which point `GuestMemory::has_outstanding_borrows()`
-/// will return `false`.
+/// methods on this trait. While a `GuestSlice` or a `GuestStr` are live,
+/// WebAssembly cannot be reentered because the store's borrow is connected to
+/// the relevant `'a` lifetime on the guest pointer.
 pub unsafe trait GuestMemory: Send + Sync {
     /// Returns the base allocation of this guest memory, located in host
     /// memory.
@@ -115,36 +111,32 @@ pub unsafe trait GuestMemory: Send + Sync {
         GuestPtr::new(self, offset)
     }
 
-    /// Indicates whether any outstanding borrows are known to the
-    /// `GuestMemory`. This function must be `false` in order for it to be
-    /// safe to recursively call into a WebAssembly module, or to manipulate
-    /// the WebAssembly memory by any other means.
-    fn has_outstanding_borrows(&self) -> bool;
-    /// Check if a region of linear memory is exclusively borrowed. This is called during any
-    /// `GuestPtr::read` or `GuestPtr::write` operation to ensure that wiggle is not reading or
-    /// writing a region of memory which Rust believes it has exclusive access to.
-    fn is_mut_borrowed(&self, r: Region) -> bool;
-    /// Check if a region of linear memory has any shared borrows.
-    fn is_shared_borrowed(&self, r: Region) -> bool;
-    /// Exclusively borrow a region of linear memory. This is used when constructing a
-    /// `GuestSliceMut` or `GuestStrMut`. Those types will give Rust `&mut` access
-    /// to the region of linear memory, therefore, the `GuestMemory` impl must
-    /// guarantee that at most one `BorrowHandle` is issued to a given region,
-    /// `GuestMemory::has_outstanding_borrows` is true for the duration of the
-    /// borrow, and that `GuestMemory::is_mut_borrowed` of any overlapping region
-    /// is false for the duration of the borrow.
+    /// Check if a region of memory can be read.
+    ///
+    /// This will only return `true` if there are no active mutable borrows.
+    fn can_read(&self, r: Region) -> bool;
+
+    /// Check if a region of memory can be written.
+    ///
+    /// This will only return `true` if there are no active borrows.
+    fn can_write(&self, r: Region) -> bool;
+
+    /// Acquires a mutable borrow on a region of memory.
+    ///
+    /// Only succeeds if there are no active shared or mutable borrows and this
+    /// is not a `shared` WebAssembly memory.
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
-    /// Shared borrow a region of linear memory. This is used when constructing a
-    /// `GuestSlice` or `GuestStr`. Those types will give Rust `&` (shared reference) access
-    /// to the region of linear memory.
+
+    /// Acquires a shared borrow on a region of memory.
+    ///
+    /// Only succeeds if there are no active mutable borrows and this is not a
+    /// `shared` WebAssembly memory.
     fn shared_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
-    /// Unborrow a previously borrowed mutable region. As long as `GuestSliceMut` and
-    /// `GuestStrMut` are implemented correctly, a mut `BorrowHandle` should only be
-    /// unborrowed once.
+
+    /// Undoes a borrow by `mut_borrow`.
     fn mut_unborrow(&self, h: BorrowHandle);
-    /// Unborrow a previously borrowed shared region. As long as `GuestSlice` and
-    /// `GuestStr` are implemented correctly, a shared `BorrowHandle` should only be
-    /// unborrowed once.
+
+    /// Undoes a borrow by `shared_borrow`.
     fn shared_unborrow(&self, h: BorrowHandle);
 
     /// Check if the underlying memory is shared across multiple threads; e.g.,
@@ -206,21 +198,20 @@ fn validate_size_align<'a, T: GuestTypeTransparent<'a>>(
 /// consumed by `{mut, shared}_unborrow`. Only the `GuestMemory` impl should ever construct
 /// a `BorrowHandle` or inspect its contents.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct BorrowHandle(pub usize);
+pub struct BorrowHandle {
+    _priv: (),
+}
 
 // Forwarding trait implementations to the original type
 unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
     fn base(&self) -> &[UnsafeCell<u8>] {
         T::base(self)
     }
-    fn has_outstanding_borrows(&self) -> bool {
-        T::has_outstanding_borrows(self)
+    fn can_read(&self, r: Region) -> bool {
+        T::can_read(self, r)
     }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        T::is_mut_borrowed(self, r)
-    }
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        T::is_shared_borrowed(self, r)
+    fn can_write(&self, r: Region) -> bool {
+        T::can_write(self, r)
     }
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
         T::mut_borrow(self, r)
@@ -240,14 +231,11 @@ unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
     fn base(&self) -> &[UnsafeCell<u8>] {
         T::base(self)
     }
-    fn has_outstanding_borrows(&self) -> bool {
-        T::has_outstanding_borrows(self)
-    }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        T::is_mut_borrowed(self, r)
+    fn can_read(&self, r: Region) -> bool {
+        T::can_read(self, r)
     }
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        T::is_shared_borrowed(self, r)
+    fn can_write(&self, r: Region) -> bool {
+        T::can_write(self, r)
     }
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
         T::mut_borrow(self, r)
@@ -267,14 +255,11 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
     fn base(&self) -> &[UnsafeCell<u8>] {
         T::base(self)
     }
-    fn has_outstanding_borrows(&self) -> bool {
-        T::has_outstanding_borrows(self)
+    fn can_read(&self, r: Region) -> bool {
+        T::can_read(self, r)
     }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        T::is_mut_borrowed(self, r)
-    }
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        T::is_shared_borrowed(self, r)
+    fn can_write(&self, r: Region) -> bool {
+        T::can_write(self, r)
     }
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
         T::mut_borrow(self, r)
@@ -294,14 +279,11 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
     fn base(&self) -> &[UnsafeCell<u8>] {
         T::base(self)
     }
-    fn has_outstanding_borrows(&self) -> bool {
-        T::has_outstanding_borrows(self)
-    }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        T::is_mut_borrowed(self, r)
+    fn can_read(&self, r: Region) -> bool {
+        T::can_read(self, r)
     }
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        T::is_shared_borrowed(self, r)
+    fn can_write(&self, r: Region) -> bool {
+        T::can_write(self, r)
     }
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
         T::mut_borrow(self, r)
@@ -538,10 +520,11 @@ impl<'a, T> GuestPtr<'a, [T]> {
 
     /// Attempts to create a [`GuestSlice<'_, T>`] from this pointer, performing
     /// bounds checks and type validation. The `GuestSlice` is a smart pointer
-    /// that can be used as a `&[T]` via the `Deref` trait. The region of memory
-    /// backing the slice will be marked as shareably borrowed by the
-    /// [`GuestMemory`] until the `GuestSlice` is dropped. Multiple shareable
-    /// borrows of the same memory are permitted, but only one mutable borrow.
+    /// that can be used as a `&[T]` via the `Deref` trait.
+    ///
+    /// This method will flag the entire linear memory as marked with a shared
+    /// borrow. This means that any writes to memory are disallowed until
+    /// the returned `GuestSlice` is dropped.
     ///
     /// This function will return a `GuestSlice` into host memory if all checks
     /// succeed (valid utf-8, valid pointers, memory is not borrowed, etc.). If
@@ -564,9 +547,11 @@ impl<'a, T> GuestPtr<'a, [T]> {
     /// Attempts to create a [`GuestSliceMut<'_, T>`] from this pointer,
     /// performing bounds checks and type validation. The `GuestSliceMut` is a
     /// smart pointer that can be used as a `&[T]` or a `&mut [T]` via the
-    /// `Deref` and `DerefMut` traits. The region of memory backing the slice
-    /// will be marked as borrowed by the [`GuestMemory`] until the `GuestSlice`
-    /// is dropped.
+    /// `Deref` and `DerefMut` traits.
+    ///
+    /// This method will flag the entire linear memory as marked with a mutable
+    /// borrow. This means that all reads/writes to memory are disallowed until
+    /// the returned `GuestSliceMut` type is dropped.
     ///
     /// This function will return a `GuestSliceMut` into host memory if all
     /// checks succeed (valid utf-8, valid pointers, memory is not borrowed,
diff --git a/crates/wiggle/src/wasmtime.rs b/crates/wiggle/src/wasmtime.rs
index 6972c5269..21933fc7a 100644
--- a/crates/wiggle/src/wasmtime.rs
+++ b/crates/wiggle/src/wasmtime.rs
@@ -63,16 +63,12 @@ unsafe impl GuestMemory for WasmtimeGuestMemory<'_> {
     // checking a flag before calling the more expensive borrow-checker methods.
 
     #[inline]
-    fn has_outstanding_borrows(&self) -> bool {
-        !self.shared && self.bc.has_outstanding_borrows()
+    fn can_read(&self, r: Region) -> bool {
+        self.shared || self.bc.can_read(r)
     }
     #[inline]
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        !self.shared && self.bc.is_shared_borrowed(r)
-    }
-    #[inline]
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        !self.shared && self.bc.is_mut_borrowed(r)
+    fn can_write(&self, r: Region) -> bool {
+        self.shared || self.bc.can_write(r)
     }
     #[inline]
     fn shared_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index c4c5b4321..369826159 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -482,6 +482,22 @@ version = "0.16.20"
 criteria = "safe-to-deploy"
 notes = "contains assembly language and object file implementations of crypto primitives for a very large number of platforms"
 
+[[exemptions.ring]]
+version = "0.17.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustls]]
+version = "0.22.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustls-pki-types]]
+version = "1.3.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustls-webpki]]
+version = "0.102.2"
+criteria = "safe-to-deploy"
+
 [[exemptions.rusty-fork]]
 version = "0.3.0"
 criteria = "safe-to-deploy"
@@ -514,6 +530,10 @@ criteria = "safe-to-deploy"
 version = "0.5.2"
 criteria = "safe-to-deploy"
 
+[[exemptions.spin]]
+version = "0.9.4"
+criteria = "safe-to-deploy"
+
 [[exemptions.stable_deref_trait]]
 version = "1.2.0"
 criteria = "safe-to-deploy"
@@ -547,6 +567,10 @@ notes = "we are exempting tokio, hyper, and their tightly coupled dependencies b
 version = "1.7.0"
 criteria = "safe-to-deploy"
 
+[[exemptions.tokio-rustls]]
+version = "0.25.0"
+criteria = "safe-to-deploy"
+
 [[exemptions.tracing]]
 version = "0.1.34"
 criteria = "safe-to-deploy"
@@ -563,6 +587,14 @@ criteria = "safe-to-deploy"
 version = "1.15.0"
 criteria = "safe-to-deploy"
 
+[[exemptions.untrusted]]
+version = "0.9.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.ureq]]
+version = "2.9.6"
+criteria = "safe-to-deploy"
+
 [[exemptions.uuid]]
 version = "1.0.0"
 criteria = "safe-to-deploy"
@@ -600,6 +632,10 @@ version = "0.3.57"
 criteria = "safe-to-deploy"
 notes = "dependency of ring for wasm32 browser platform, which our project does not target"
 
+[[exemptions.webpki-roots]]
+version = "0.26.1"
+criteria = "safe-to-deploy"
+
 [[exemptions.winapi]]
 version = "0.3.9"
 criteria = "safe-to-deploy"
@@ -620,6 +656,10 @@ criteria = "safe-to-deploy"
 version = "0.7.32"
 criteria = "safe-to-deploy"
 
+[[exemptions.zeroize]]
+version = "1.7.0"
+criteria = "safe-to-deploy"
+
 [[exemptions.zstd]]
 version = "0.11.1+zstd.1.5.2"
 criteria = "safe-to-deploy"
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 6226ab23d..a4490981e 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -2021,6 +2021,13 @@ criteria = "safe-to-deploy"
 delta = "0.4.6 -> 0.4.7"
 aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
 
+[[audits.mozilla.audits.subtle]]
+who = "Simon Friedberger <simon@mozilla.com>"
+criteria = "safe-to-deploy"
+version = "2.5.0"
+notes = "The goal is to provide some constant-time correctness for cryptographic implementations. The approach is reasonable, it is known to be insufficient but this is pointed out in the documentation."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
 [[audits.mozilla.audits.tempfile]]
 who = "Mike Hommey <mh+mozilla@glandium.org>"
 criteria = "safe-to-deploy"


Test patch:
diff --git a/cranelift/filetests/filetests/isa/aarch64/return-call-indirect.clif b/cranelift/filetests/filetests/isa/aarch64/return-call-indirect.clif
new file mode 100644
index 000000000..d02b2b868
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/aarch64/return-call-indirect.clif
@@ -0,0 +1,316 @@
+test compile precise-output
+
+target aarch64
+
+;;;; Test passing `i64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i64(i64) -> i64 tail {
+block0(v0: i64):
+    v1 = iadd_imm.i64 v0, 10
+    return v1
+}
+
+; VCode:
+; block0:
+;   add x2, x2, #10
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   add x2, x2, #0xa
+;   ret
+
+function %call_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   stp fp, lr, [sp, #-16]!
+;   mov fp, sp
+; block0:
+;   load_ext_name x3, TestCase(%callee_i64)+0
+;   return_call_ind x3 old_stack_arg_size:0 new_stack_arg_size:0 x2=x2
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   stp x29, x30, [sp, #-0x10]!
+;   mov x29, sp
+; block1: ; offset 0x8
+;   ldr x3, #0x10
+;   b #0x18
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ldp x16, x30, [x29]
+;   add sp, x29, #0x10
+;   mov x29, x16
+;   br x3
+
+;;;; Test colocated tail calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %colocated_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = colocated %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   stp fp, lr, [sp, #-16]!
+;   mov fp, sp
+; block0:
+;   load_ext_name x3, TestCase(%callee_i64)+0
+;   return_call_ind x3 old_stack_arg_size:0 new_stack_arg_size:0 x2=x2
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   stp x29, x30, [sp, #-0x10]!
+;   mov x29, sp
+; block1: ; offset 0x8
+;   ldr x3, #0x10
+;   b #0x18
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ldp x16, x30, [x29]
+;   add sp, x29, #0x10
+;   mov x29, x16
+;   br x3
+
+;;;; Test passing `f64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_f64(f64) -> f64 tail {
+block0(v0: f64):
+    v1 = f64const 0x10.0
+    v2 = fadd.f64 v0, v1
+    return v2
+}
+
+; VCode:
+; block0:
+;   fmov d3, #16
+;   fadd d0, d0, d3
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   fmov d3, #16.00000000
+;   fadd d0, d0, d3
+;   ret
+
+function %call_f64(f64) -> f64 tail {
+    sig0 = (f64) -> f64 tail
+    fn0 = %callee_f64(f64) -> f64 tail
+
+block0(v0: f64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   stp fp, lr, [sp, #-16]!
+;   mov fp, sp
+; block0:
+;   load_ext_name x2, TestCase(%callee_f64)+0
+;   return_call_ind x2 old_stack_arg_size:0 new_stack_arg_size:0 v0=v0
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   stp x29, x30, [sp, #-0x10]!
+;   mov x29, sp
+; block1: ; offset 0x8
+;   ldr x2, #0x10
+;   b #0x18
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_f64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ldp x16, x30, [x29]
+;   add sp, x29, #0x10
+;   mov x29, x16
+;   br x2
+
+;;;; Test passing `i8`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i8(i8) -> i8 tail {
+block0(v0: i8):
+    v1 = iconst.i8 0
+    v2 = icmp eq v0, v1
+    return v2
+}
+
+; VCode:
+; block0:
+;   uxtb w2, w2
+;   subs wzr, w2, #0
+;   cset x2, eq
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   uxtb w2, w2
+;   cmp w2, #0
+;   cset x2, eq
+;   ret
+
+function %call_i8(i8) -> i8 tail {
+    sig0 = (i8) -> i8 tail
+    fn0 = %callee_i8(i8) -> i8 tail
+
+block0(v0: i8):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   stp fp, lr, [sp, #-16]!
+;   mov fp, sp
+; block0:
+;   load_ext_name x3, TestCase(%callee_i8)+0
+;   return_call_ind x3 old_stack_arg_size:0 new_stack_arg_size:0 x2=x2
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   stp x29, x30, [sp, #-0x10]!
+;   mov x29, sp
+; block1: ; offset 0x8
+;   ldr x3, #0x10
+;   b #0x18
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i8 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ldp x16, x30, [x29]
+;   add sp, x29, #0x10
+;   mov x29, x16
+;   br x3
+
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    v26 = func_addr.i64 fn0
+    return_call_indirect sig0, v26(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+
+; VCode:
+;   stp fp, lr, [sp, #-16]!
+;   mov fp, sp
+;   sub sp, sp, #16
+; block0:
+;   movz x2, #10
+;   movz x3, #15
+;   movz x4, #20
+;   movz x5, #25
+;   movz x6, #30
+;   movz x7, #35
+;   movz x8, #40
+;   movz x9, #45
+;   movz x10, #50
+;   str x10, [sp]
+;   movz x11, #55
+;   movz x12, #60
+;   movz x13, #65
+;   movz x14, #70
+;   movz x15, #75
+;   movz x19, #80
+;   movz x20, #85
+;   movz x21, #90
+;   movz x22, #95
+;   movz x23, #100
+;   movz x24, #105
+;   movz x25, #110
+;   movz x26, #115
+;   movz x27, #120
+;   movz x28, #125
+;   movz x0, #130
+;   movz x1, #135
+;   load_ext_name x10, TestCase(%tail_callee_stack_args)+0
+;   sub sp, sp, #16
+;   virtual_sp_offset_adjust 16
+;   str x0, [sp]
+;   str x1, [sp, #8]
+;   mov x0, x10
+;   ldr x10, [sp, #16]
+;   return_call_ind x0 old_stack_arg_size:0 new_stack_arg_size:16 x2=x2 x3=x3 x4=x4 x5=x5 x6=x6 x7=x7 x8=x8 x9=x9 x10=x10 x11=x11 x12=x12 x13=x13 x14=x14 x15=x15 x19=x19 x20=x20 x21=x21 x22=x22 x23=x23 x24=x24 x25=x25 x26=x26 x27=x27 x28=x28
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   stp x29, x30, [sp, #-0x10]!
+;   mov x29, sp
+;   sub sp, sp, #0x10
+; block1: ; offset 0xc
+;   mov x2, #0xa
+;   mov x3, #0xf
+;   mov x4, #0x14
+;   mov x5, #0x19
+;   mov x6, #0x1e
+;   mov x7, #0x23
+;   mov x8, #0x28
+;   mov x9, #0x2d
+;   mov x10, #0x32
+;   stur x10, [sp]
+;   mov x11, #0x37
+;   mov x12, #0x3c
+;   mov x13, #0x41
+;   mov x14, #0x46
+;   mov x15, #0x4b
+;   mov x19, #0x50
+;   mov x20, #0x55
+;   mov x21, #0x5a
+;   mov x22, #0x5f
+;   mov x23, #0x64
+;   mov x24, #0x69
+;   mov x25, #0x6e
+;   mov x26, #0x73
+;   mov x27, #0x78
+;   mov x28, #0x7d
+;   mov x0, #0x82
+;   mov x1, #0x87
+;   ldr x10, #0x80
+;   b #0x88
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %tail_callee_stack_args 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   sub sp, sp, #0x10
+;   stur x0, [sp]
+;   stur x1, [sp, #8]
+;   mov x0, x10
+;   ldur x10, [sp, #0x10]
+;   ldp x16, x30, [x29]
+;   ldur x17, [sp, #8]
+;   stur x17, [x29, #8]
+;   ldur x17, [sp]
+;   stur x17, [x29]
+;   mov sp, x29
+;   mov x29, x16
+;   br x0
+
diff --git a/cranelift/filetests/filetests/isa/riscv64/return-call-indirect.clif b/cranelift/filetests/filetests/isa/riscv64/return-call-indirect.clif
new file mode 100644
index 000000000..f23a47133
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/riscv64/return-call-indirect.clif
@@ -0,0 +1,370 @@
+test compile precise-output
+
+target riscv64
+
+;;;; Test passing `i64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i64(i64) -> i64 tail {
+block0(v0: i64):
+    v1 = iadd_imm.i64 v0, 10
+    return v1
+}
+
+; VCode:
+; block0:
+;   addi s1,s1,10
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi s1, s1, 0xa
+;   ret
+
+function %call_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+; block0:
+;   load_sym a2,%callee_i64+0
+;   return_call_ind a2 old_stack_arg_size:0 new_stack_arg_size:0 s1=s1
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+; block1: ; offset 0x10
+;   auipc a2, 0
+;   ld a2, 0xc(a2)
+;   j 0xc
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ld ra, 8(s0)
+;   ld t6, 0(s0)
+;   addi sp, s0, 0x10
+;   mv s0, t6
+;   jr a2
+
+;;;; Test colocated tail calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %colocated_i64(i64) -> i64 tail {
+    sig0 = (i64) -> i64 tail
+    fn0 = colocated %callee_i64(i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+; block0:
+;   load_sym a2,%callee_i64+0
+;   return_call_ind a2 old_stack_arg_size:0 new_stack_arg_size:0 s1=s1
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+; block1: ; offset 0x10
+;   auipc a2, 0
+;   ld a2, 0xc(a2)
+;   j 0xc
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ld ra, 8(s0)
+;   ld t6, 0(s0)
+;   addi sp, s0, 0x10
+;   mv s0, t6
+;   jr a2
+
+;;;; Test passing `f64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_f64(f64) -> f64 tail {
+block0(v0: f64):
+    v1 = f64const 0x10.0
+    v2 = fadd.f64 v0, v1
+    return v2
+}
+
+; VCode:
+; block0:
+;   lui a3,1027
+;   slli a5,a3,40
+;   fmv.d.x fa1,a5
+;   fadd.d ft0,ft0,fa1,rne
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lui a3, 0x403
+;   slli a5, a3, 0x28
+;   fmv.d.x fa1, a5
+;   fadd.d ft0, ft0, fa1, rne
+;   ret
+
+function %call_f64(f64) -> f64 tail {
+    sig0 = (f64) -> f64 tail
+    fn0 = %callee_f64(f64) -> f64 tail
+
+block0(v0: f64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+; block0:
+;   load_sym a2,%callee_f64+0
+;   return_call_ind a2 old_stack_arg_size:0 new_stack_arg_size:0 ft0=ft0
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+; block1: ; offset 0x10
+;   auipc a2, 0
+;   ld a2, 0xc(a2)
+;   j 0xc
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_f64 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ld ra, 8(s0)
+;   ld t6, 0(s0)
+;   addi sp, s0, 0x10
+;   mv s0, t6
+;   jr a2
+
+;;;; Test passing `i8`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i8(i8) -> i8 tail {
+block0(v0: i8):
+    v1 = iconst.i8 0
+    v2 = icmp eq v0, v1
+    return v2
+}
+
+; VCode:
+; block0:
+;   andi a2,s1,255
+;   seqz s1,a2
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   andi a2, s1, 0xff
+;   seqz s1, a2
+;   ret
+
+function %call_i8(i8) -> i8 tail {
+    sig0 = (i8) -> i8 tail
+    fn0 = %callee_i8(i8) -> i8 tail
+
+block0(v0: i8):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+; block0:
+;   load_sym a2,%callee_i8+0
+;   return_call_ind a2 old_stack_arg_size:0 new_stack_arg_size:0 s1=s1
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+; block1: ; offset 0x10
+;   auipc a2, 0
+;   ld a2, 0xc(a2)
+;   j 0xc
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %callee_i8 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   ld ra, 8(s0)
+;   ld t6, 0(s0)
+;   addi sp, s0, 0x10
+;   mv s0, t6
+;   jr a2
+
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    v26 = func_addr.i64 fn0
+    return_call_indirect sig0, v26(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+
+; VCode:
+;   addi sp,sp,-16
+;   sd ra,8(sp)
+;   sd fp,0(sp)
+;   mv fp,sp
+;   addi sp,sp,-32
+; block0:
+;   li s1,10
+;   sd s1,16(nominal_sp)
+;   li a0,15
+;   sd a0,8(nominal_sp)
+;   li a1,20
+;   li a2,25
+;   sd a2,0(nominal_sp)
+;   li a3,30
+;   li a4,35
+;   li a5,40
+;   li a6,45
+;   li a7,50
+;   li s2,55
+;   li s3,60
+;   li s4,65
+;   li s5,70
+;   li s6,75
+;   li s7,80
+;   li s8,85
+;   li s9,90
+;   li s10,95
+;   li s11,100
+;   li t3,105
+;   li t4,110
+;   li t0,115
+;   li t1,120
+;   li t2,125
+;   li s1,130
+;   li a0,135
+;   load_sym a2,%tail_callee_stack_args+0
+;   addi sp,sp,-48
+;   virtual_sp_offset_adj +48
+;   sd t0,0(sp)
+;   sd t1,8(sp)
+;   sd t2,16(sp)
+;   sd s1,24(sp)
+;   sd a0,32(sp)
+;   ld a0,8(nominal_sp)
+;   ld s1,16(nominal_sp)
+;   mv t0,a2
+;   ld a2,0(nominal_sp)
+;   return_call_ind t0 old_stack_arg_size:0 new_stack_arg_size:48 s1=s1 a0=a0 a1=a1 a2=a2 a3=a3 a4=a4 a5=a5 a6=a6 a7=a7 s2=s2 s3=s3 s4=s4 s5=s5 s6=s6 s7=s7 s8=s8 s9=s9 s10=s10 s11=s11 t3=t3 t4=t4
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   addi sp, sp, -0x10
+;   sd ra, 8(sp)
+;   sd s0, 0(sp)
+;   mv s0, sp
+;   addi sp, sp, -0x20
+; block1: ; offset 0x14
+;   addi s1, zero, 0xa
+;   sd s1, 0x10(sp)
+;   addi a0, zero, 0xf
+;   sd a0, 8(sp)
+;   addi a1, zero, 0x14
+;   addi a2, zero, 0x19
+;   sd a2, 0(sp)
+;   addi a3, zero, 0x1e
+;   addi a4, zero, 0x23
+;   addi a5, zero, 0x28
+;   addi a6, zero, 0x2d
+;   addi a7, zero, 0x32
+;   addi s2, zero, 0x37
+;   addi s3, zero, 0x3c
+;   addi s4, zero, 0x41
+;   addi s5, zero, 0x46
+;   addi s6, zero, 0x4b
+;   addi s7, zero, 0x50
+;   addi s8, zero, 0x55
+;   addi s9, zero, 0x5a
+;   addi s10, zero, 0x5f
+;   addi s11, zero, 0x64
+;   addi t3, zero, 0x69
+;   addi t4, zero, 0x6e
+;   addi t0, zero, 0x73
+;   addi t1, zero, 0x78
+;   addi t2, zero, 0x7d
+;   addi s1, zero, 0x82
+;   addi a0, zero, 0x87
+;   auipc a2, 0
+;   ld a2, 0xc(a2)
+;   j 0xc
+;   .byte 0x00, 0x00, 0x00, 0x00 ; reloc_external Abs8 %tail_callee_stack_args 0
+;   .byte 0x00, 0x00, 0x00, 0x00
+;   addi sp, sp, -0x30
+;   sd t0, 0(sp)
+;   sd t1, 8(sp)
+;   sd t2, 0x10(sp)
+;   sd s1, 0x18(sp)
+;   sd a0, 0x20(sp)
+;   ld a0, 0x38(sp)
+;   ld s1, 0x40(sp)
+;   mv t0, a2
+;   ld a2, 0x30(sp)
+;   ld ra, 8(s0)
+;   ld t6, 0(s0)
+;   ld t5, 0x28(sp)
+;   sd t5, 8(s0)
+;   ld t5, 0x20(sp)
+;   sd t5, 0(s0)
+;   ld t5, 0x18(sp)
+;   sd t5, -8(s0)
+;   ld t5, 0x10(sp)
+;   sd t5, -0x10(s0)
+;   ld t5, 8(sp)
+;   sd t5, -0x18(s0)
+;   ld t5, 0(sp)
+;   sd t5, -0x20(s0)
+;   addi sp, s0, -0x20
+;   mv s0, t6
+;   jr t0
+
diff --git a/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif b/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif
index df1c39b96..b79abf8e0 100644
--- a/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif
+++ b/cranelift/filetests/filetests/isa/x64/return-call-indirect.clif
@@ -208,3 +208,269 @@ block0(v0: i8):
 ;   popq %rbp
 ;   jmpq *%rax
 
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    v26 = func_addr.i64 fn0
+    return_call_indirect sig0, v26(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+;   subq    %rsp, $160, %rsp
+;   movq    %rbx, 112(%rsp)
+;   movq    %r12, 120(%rsp)
+;   movq    %r13, 128(%rsp)
+;   movq    %r14, 136(%rsp)
+;   movq    %r15, 144(%rsp)
+; block0:
+;   movl    $10, %edi
+;   movq    %rdi, rsp(104 + virtual offset)
+;   movl    $15, %esi
+;   movq    %rsi, rsp(96 + virtual offset)
+;   movl    $20, %edx
+;   movq    %rdx, rsp(88 + virtual offset)
+;   movl    $25, %ecx
+;   movq    %rcx, rsp(80 + virtual offset)
+;   movl    $30, %r8d
+;   movq    %r8, rsp(72 + virtual offset)
+;   movl    $35, %r9d
+;   movq    %r9, rsp(64 + virtual offset)
+;   movl    $40, %eax
+;   movl    $45, %r10d
+;   movl    $50, %r11d
+;   movl    $55, %r13d
+;   movl    $60, %r14d
+;   movl    $65, %r15d
+;   movl    $70, %ebx
+;   movl    $75, %r12d
+;   movl    $80, %edi
+;   movl    $85, %esi
+;   movq    %rsi, rsp(56 + virtual offset)
+;   movl    $90, %edx
+;   movl    $95, %ecx
+;   movl    $100, %r8d
+;   movl    $105, %r9d
+;   movl    $110, %esi
+;   movq    %rsi, rsp(48 + virtual offset)
+;   movl    $115, %esi
+;   movq    %rsi, rsp(40 + virtual offset)
+;   movl    $120, %esi
+;   movq    %rsi, rsp(32 + virtual offset)
+;   movl    $125, %esi
+;   movq    %rsi, rsp(24 + virtual offset)
+;   movl    $130, %esi
+;   movq    %rsi, rsp(16 + virtual offset)
+;   movl    $135, %esi
+;   movq    %rsi, rsp(8 + virtual offset)
+;   load_ext_name %tail_callee_stack_args+0, %rsi
+;   movq    %rsi, rsp(0 + virtual offset)
+;   grow_argument_area 160 %rsi
+;   movq    %rax, 16(%rbp)
+;   movq    %r10, 24(%rbp)
+;   movq    %r11, 32(%rbp)
+;   movq    %r13, 40(%rbp)
+;   movq    %r14, 48(%rbp)
+;   movq    %r15, 56(%rbp)
+;   movq    %rbx, 64(%rbp)
+;   movq    %r12, 72(%rbp)
+;   movq    %rdi, 80(%rbp)
+;   movq    rsp(56 + virtual offset), %rdi
+;   movq    %rdi, 88(%rbp)
+;   movq    %rdx, 96(%rbp)
+;   movq    %rcx, 104(%rbp)
+;   movq    %r8, 112(%rbp)
+;   movq    %r9, 120(%rbp)
+;   movq    rsp(48 + virtual offset), %rsi
+;   movq    %rsi, 128(%rbp)
+;   movq    rsp(40 + virtual offset), %rsi
+;   movq    %rsi, 136(%rbp)
+;   movq    rsp(32 + virtual offset), %rsi
+;   movq    %rsi, 144(%rbp)
+;   movq    rsp(24 + virtual offset), %rsi
+;   movq    %rsi, 152(%rbp)
+;   movq    rsp(16 + virtual offset), %rsi
+;   movq    %rsi, 160(%rbp)
+;   movq    rsp(8 + virtual offset), %rsi
+;   movq    %rsi, 168(%rbp)
+;   movq    rsp(0 + virtual offset), %rax
+;   movq    rsp(80 + virtual offset), %rcx
+;   movq    rsp(88 + virtual offset), %rdx
+;   movq    rsp(96 + virtual offset), %rsi
+;   movq    rsp(104 + virtual offset), %rdi
+;   movq    rsp(72 + virtual offset), %r8
+;   movq    rsp(64 + virtual offset), %r9
+;   return_call_unknown %rax %rdi=%rdi %rsi=%rsi %rdx=%rdx %rcx=%rcx %r8=%r8 %r9=%r9
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+;   subq $0xa0, %rsp
+;   movq %rbx, 0x70(%rsp)
+;   movq %r12, 0x78(%rsp)
+;   movq %r13, 0x80(%rsp)
+;   movq %r14, 0x88(%rsp)
+;   movq %r15, 0x90(%rsp)
+; block1: ; offset 0x2d
+;   movl $0xa, %edi
+;   movq %rdi, 0x68(%rsp)
+;   movl $0xf, %esi
+;   movq %rsi, 0x60(%rsp)
+;   movl $0x14, %edx
+;   movq %rdx, 0x58(%rsp)
+;   movl $0x19, %ecx
+;   movq %rcx, 0x50(%rsp)
+;   movl $0x1e, %r8d
+;   movq %r8, 0x48(%rsp)
+;   movl $0x23, %r9d
+;   movq %r9, 0x40(%rsp)
+;   movl $0x28, %eax
+;   movl $0x2d, %r10d
+;   movl $0x32, %r11d
+;   movl $0x37, %r13d
+;   movl $0x3c, %r14d
+;   movl $0x41, %r15d
+;   movl $0x46, %ebx
+;   movl $0x4b, %r12d
+;   movl $0x50, %edi
+;   movl $0x55, %esi
+;   movq %rsi, 0x38(%rsp)
+;   movl $0x5a, %edx
+;   movl $0x5f, %ecx
+;   movl $0x64, %r8d
+;   movl $0x69, %r9d
+;   movl $0x6e, %esi
+;   movq %rsi, 0x30(%rsp)
+;   movl $0x73, %esi
+;   movq %rsi, 0x28(%rsp)
+;   movl $0x78, %esi
+;   movq %rsi, 0x20(%rsp)
+;   movl $0x7d, %esi
+;   movq %rsi, 0x18(%rsp)
+;   movl $0x82, %esi
+;   movq %rsi, 0x10(%rsp)
+;   movl $0x87, %esi
+;   movq %rsi, 8(%rsp)
+;   movabsq $0, %rsi ; reloc_external Abs8 %tail_callee_stack_args 0
+;   movq %rsi, (%rsp)
+;   subq $0xa0, %rsp
+;   subq $0xa0, %rbp
+;   movq 0xa0(%rsp), %rsi
+;   movq %rsi, (%rsp)
+;   movq 0xa8(%rsp), %rsi
+;   movq %rsi, 8(%rsp)
+;   movq 0xb0(%rsp), %rsi
+;   movq %rsi, 0x10(%rsp)
+;   movq 0xb8(%rsp), %rsi
+;   movq %rsi, 0x18(%rsp)
+;   movq 0xc0(%rsp), %rsi
+;   movq %rsi, 0x20(%rsp)
+;   movq 0xc8(%rsp), %rsi
+;   movq %rsi, 0x28(%rsp)
+;   movq 0xd0(%rsp), %rsi
+;   movq %rsi, 0x30(%rsp)
+;   movq 0xd8(%rsp), %rsi
+;   movq %rsi, 0x38(%rsp)
+;   movq 0xe0(%rsp), %rsi
+;   movq %rsi, 0x40(%rsp)
+;   movq 0xe8(%rsp), %rsi
+;   movq %rsi, 0x48(%rsp)
+;   movq 0xf0(%rsp), %rsi
+;   movq %rsi, 0x50(%rsp)
+;   movq 0xf8(%rsp), %rsi
+;   movq %rsi, 0x58(%rsp)
+;   movq 0x100(%rsp), %rsi
+;   movq %rsi, 0x60(%rsp)
+;   movq 0x108(%rsp), %rsi
+;   movq %rsi, 0x68(%rsp)
+;   movq 0x110(%rsp), %rsi
+;   movq %rsi, 0x70(%rsp)
+;   movq 0x118(%rsp), %rsi
+;   movq %rsi, 0x78(%rsp)
+;   movq 0x120(%rsp), %rsi
+;   movq %rsi, 0x80(%rsp)
+;   movq 0x128(%rsp), %rsi
+;   movq %rsi, 0x88(%rsp)
+;   movq 0x130(%rsp), %rsi
+;   movq %rsi, 0x90(%rsp)
+;   movq 0x138(%rsp), %rsi
+;   movq %rsi, 0x98(%rsp)
+;   movq 0x140(%rsp), %rsi
+;   movq %rsi, 0xa0(%rsp)
+;   movq 0x148(%rsp), %rsi
+;   movq %rsi, 0xa8(%rsp)
+;   movq %rax, 0x10(%rbp)
+;   movq %r10, 0x18(%rbp)
+;   movq %r11, 0x20(%rbp)
+;   movq %r13, 0x28(%rbp)
+;   movq %r14, 0x30(%rbp)
+;   movq %r15, 0x38(%rbp)
+;   movq %rbx, 0x40(%rbp)
+;   movq %r12, 0x48(%rbp)
+;   movq %rdi, 0x50(%rbp)
+;   movq 0x38(%rsp), %rdi
+;   movq %rdi, 0x58(%rbp)
+;   movq %rdx, 0x60(%rbp)
+;   movq %rcx, 0x68(%rbp)
+;   movq %r8, 0x70(%rbp)
+;   movq %r9, 0x78(%rbp)
+;   movq 0x30(%rsp), %rsi
+;   movq %rsi, 0x80(%rbp)
+;   movq 0x28(%rsp), %rsi
+;   movq %rsi, 0x88(%rbp)
+;   movq 0x20(%rsp), %rsi
+;   movq %rsi, 0x90(%rbp)
+;   movq 0x18(%rsp), %rsi
+;   movq %rsi, 0x98(%rbp)
+;   movq 0x10(%rsp), %rsi
+;   movq %rsi, 0xa0(%rbp)
+;   movq 8(%rsp), %rsi
+;   movq %rsi, 0xa8(%rbp)
+;   movq (%rsp), %rax
+;   movq 0x50(%rsp), %rcx
+;   movq 0x58(%rsp), %rdx
+;   movq 0x60(%rsp), %rsi
+;   movq 0x68(%rsp), %rdi
+;   movq 0x48(%rsp), %r8
+;   movq 0x40(%rsp), %r9
+;   movq 0x70(%rsp), %rbx
+;   movq 0x78(%rsp), %r12
+;   movq 0x80(%rsp), %r13
+;   movq 0x88(%rsp), %r14
+;   movq 0x90(%rsp), %r15
+;   addq $0xa0, %rsp
+;   movq %rbp, %rsp
+;   popq %rbp
+;   jmpq *%rax
+
diff --git a/cranelift/filetests/filetests/runtests/return-call-indirect.clif b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
index 155e5f25c..c63922e01 100644
--- a/cranelift/filetests/filetests/runtests/return-call-indirect.clif
+++ b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
@@ -79,3 +79,45 @@ block0(v0: i8):
 }
 ; run: %call_i8(1) == 0
 ; run: %call_i8(0) == 1
+
+;;;; Test passing many arguments on stack ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail {
+block0(v0: i64, v1: i64, v2: i64, v3: i64, v4: i64, v5: i64, v6: i64, v7: i64, v8: i64, v9: i64, v10: i64, v11: i64, v12: i64, v13: i64, v14: i64, v15: i64, v16: i64, v17: i64, v18: i64, v19: i64, v20: i64, v21: i64, v22: i64, v23: i64, v24: i64, v25: i64):
+    return v25
+}
+
+function %tail_caller_stack_args() -> i64 tail {
+    fn0 = %tail_callee_stack_args(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) -> i64 tail
+
+block0:
+    v0 = iconst.i64 10
+    v1 = iconst.i64 15
+    v2 = iconst.i64 20
+    v3 = iconst.i64 25
+    v4 = iconst.i64 30
+    v5 = iconst.i64 35
+    v6 = iconst.i64 40
+    v7 = iconst.i64 45
+    v8 = iconst.i64 50
+    v9 = iconst.i64 55
+    v10 = iconst.i64 60
+    v11 = iconst.i64 65
+    v12 = iconst.i64 70
+    v13 = iconst.i64 75
+    v14 = iconst.i64 80
+    v15 = iconst.i64 85
+    v16 = iconst.i64 90
+    v17 = iconst.i64 95
+    v18 = iconst.i64 100
+    v19 = iconst.i64 105
+    v20 = iconst.i64 110
+    v21 = iconst.i64 115
+    v22 = iconst.i64 120
+    v23 = iconst.i64 125
+    v24 = iconst.i64 130
+    v25 = iconst.i64 135
+    v26 = func_addr.i64 fn0
+    return_call_indirect sig0, v26(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)
+}
+; run: %tail_caller_stack_args() == 135
diff --git a/crates/test-programs/src/bin/preview2_file_read_write.rs b/crates/test-programs/src/bin/preview2_file_read_write.rs
new file mode 100644
index 000000000..05a32a65b
--- /dev/null
+++ b/crates/test-programs/src/bin/preview2_file_read_write.rs
@@ -0,0 +1,28 @@
+use test_programs::wasi;
+use test_programs::wasi::filesystem::types::{DescriptorFlags, OpenFlags, PathFlags};
+
+fn main() {
+    let preopens = wasi::filesystem::preopens::get_directories();
+    let (dir, _) = &preopens[0];
+
+    let filename = "test.txt";
+    let file = dir
+        .open_at(
+            PathFlags::empty(),
+            filename,
+            OpenFlags::CREATE,
+            DescriptorFlags::READ | DescriptorFlags::WRITE,
+        )
+        .unwrap();
+    let stream = file.write_via_stream(5).unwrap();
+    stream.blocking_write_and_flush(b"Hello, ").unwrap();
+    stream.blocking_write_and_flush(b"World!").unwrap();
+    drop(stream);
+
+    let stream = file.read_via_stream(0).unwrap();
+    let contents = stream.blocking_read(100).unwrap();
+    assert_eq!(contents, b"\0\0\0\0\0Hello, World!");
+    drop((stream, file));
+
+    dir.unlink_file_at(filename).unwrap();
+}
diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs
index 6af7036cb..b6efb44d3 100644
--- a/crates/wasi/tests/all/async_.rs
+++ b/crates/wasi/tests/all/async_.rs
@@ -13,7 +13,11 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     let mut linker = Linker::new(&engine);
     add_to_linker_async(&mut linker)?;
 
-    let (mut store, _td) = store(&engine, name, inherit_stdio)?;
+    let (mut store, _td) = store(&engine, name, |builder| {
+        if inherit_stdio {
+            builder.inherit_stdio();
+        }
+    })?;
     let component = Component::from_file(&engine, path)?;
     let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?;
     command
@@ -379,3 +383,9 @@ async fn preview2_pollable_traps() {
 async fn preview2_adapter_badfd() {
     run(PREVIEW2_ADAPTER_BADFD_COMPONENT, false).await.unwrap()
 }
+#[test_log::test(tokio::test(flavor = "multi_thread"))]
+async fn preview2_file_read_write() {
+    run(PREVIEW2_FILE_READ_WRITE_COMPONENT, false)
+        .await
+        .unwrap()
+}
diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs
index 312ba1c7a..c5635acae 100644
--- a/crates/wasi/tests/all/main.rs
+++ b/crates/wasi/tests/all/main.rs
@@ -30,18 +30,18 @@ fn prepare_workspace(exe_name: &str) -> Result<TempDir> {
     Ok(tempdir)
 }
 
-fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>, TempDir)> {
+fn store(
+    engine: &Engine,
+    name: &str,
+    configure: impl FnOnce(&mut WasiCtxBuilder),
+) -> Result<(Store<Ctx>, TempDir)> {
     let stdout = MemoryOutputPipe::new(4096);
     let stderr = MemoryOutputPipe::new(4096);
     let workspace = prepare_workspace(name)?;
 
     // Create our wasi context.
     let mut builder = WasiCtxBuilder::new();
-    if inherit_stdio {
-        builder.inherit_stdio();
-    } else {
-        builder.stdout(stdout.clone()).stderr(stderr.clone());
-    }
+    builder.stdout(stdout.clone()).stderr(stderr.clone());
 
     builder
         .args(&[name, "."])
@@ -53,6 +53,7 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>
         builder.env(var, val);
     }
 
+    configure(&mut builder);
     let ctx = Ctx {
         wasi: builder.build_p1(),
         stderr,
diff --git a/crates/wasi/tests/all/preview1.rs b/crates/wasi/tests/all/preview1.rs
index 169c764ca..b5f67c23e 100644
--- a/crates/wasi/tests/all/preview1.rs
+++ b/crates/wasi/tests/all/preview1.rs
@@ -14,7 +14,11 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     add_to_linker_async(&mut linker, |t| &mut t.wasi)?;
 
     let module = Module::from_file(&engine, path)?;
-    let (mut store, _td) = store(&engine, name, inherit_stdio)?;
+    let (mut store, _td) = store(&engine, name, |builder| {
+        if inherit_stdio {
+            builder.inherit_stdio();
+        }
+    })?;
     let instance = linker.instantiate_async(&mut store, &module).await?;
     let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
     start.call_async(&mut store, ()).await?;
diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs
index a67c4dfce..ab89ff77f 100644
--- a/crates/wasi/tests/all/sync.rs
+++ b/crates/wasi/tests/all/sync.rs
@@ -13,13 +13,22 @@ fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     let mut linker = Linker::new(&engine);
     add_to_linker_sync(&mut linker)?;
 
-    let (mut store, _td) = store(&engine, name, inherit_stdio)?;
     let component = Component::from_file(&engine, path)?;
-    let (command, _instance) = Command::instantiate(&mut store, &component, &linker)?;
-    command
-        .wasi_cli_run()
-        .call_run(&mut store)?
-        .map_err(|()| anyhow::anyhow!("run returned a failure"))
+
+    for blocking in [false, true] {
+        let (mut store, _td) = store(&engine, name, |builder| {
+            if inherit_stdio {
+                builder.inherit_stdio();
+            }
+            builder.allow_blocking_current_thread(blocking);
+        })?;
+        let (command, _instance) = Command::instantiate(&mut store, &component, &linker)?;
+        command
+            .wasi_cli_run()
+            .call_run(&mut store)?
+            .map_err(|()| anyhow::anyhow!("run returned a failure"))?;
+    }
+    Ok(())
 }
 
 foreach_preview1!(assert_test_exists);
@@ -314,3 +323,7 @@ fn preview2_pollable_traps() {
 fn preview2_adapter_badfd() {
     run(PREVIEW2_ADAPTER_BADFD_COMPONENT, false).unwrap()
 }
+#[test_log::test]
+fn preview2_file_read_write() {
+    run(PREVIEW2_FILE_READ_WRITE_COMPONENT, false).unwrap()
+}
diff --git a/crates/wiggle/test-helpers/src/lib.rs b/crates/wiggle/test-helpers/src/lib.rs
index 0a29b3a56..d51685be7 100644
--- a/crates/wiggle/test-helpers/src/lib.rs
+++ b/crates/wiggle/test-helpers/src/lib.rs
@@ -121,14 +121,11 @@ unsafe impl GuestMemory for HostMemory {
         let ptr = self.buffer.cell.get();
         unsafe { std::slice::from_raw_parts(ptr.cast(), (*ptr).len()) }
     }
-    fn has_outstanding_borrows(&self) -> bool {
-        self.bc.has_outstanding_borrows()
+    fn can_read(&self, r: Region) -> bool {
+        self.bc.can_read(r)
     }
-    fn is_shared_borrowed(&self, r: Region) -> bool {
-        self.bc.is_shared_borrowed(r)
-    }
-    fn is_mut_borrowed(&self, r: Region) -> bool {
-        self.bc.is_mut_borrowed(r)
+    fn can_write(&self, r: Region) -> bool {
+        self.bc.can_write(r)
     }
     fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
         self.bc.mut_borrow(r)