Repository: bytecodealliance/wasmtime
Pull request #7499 (2023-11-08 15:26:33)

winch: Properly handle unconditional jumps
This commit improves unconditional jumps by balancing the stack pointer as well as the value stack when the current stack pointer and value stack are greater than the target stack pointer and value stack. The invariant that this changes maintains is that the the value stack should always reflect the the state of the machine stack. The value stack might have excess stack values in a presence of a fallthrough (`br_if` or `br_table`) in which the target branch is not known at compile time; in this situation instructions like `return` or `br` discard any excess values.

The fuzzer discovered the following test case, which triggered several assertions related to unconditional jump handling:

```wat
(module
  (func (;0;) (result i32)
    (local i32)
    local.get 0
    loop ;; label = @1
      local.get 0
      block ;; label = @2
      end
      br 0 (;@1;)
    end
  )
  (export "" (func 0))
)
```

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs
index dab240c3a..2d5802572 100644
--- a/cranelift/codegen/src/egraph.rs
+++ b/cranelift/codegen/src/egraph.rs
@@ -424,7 +424,7 @@ impl<'a> EgraphPass<'a> {
                 }
             }
         }
-        trace!("stats: {:?}", self.stats);
+        trace!("stats: {:#?}", self.stats);
         self.elaborate();
     }
 
diff --git a/cranelift/codegen/src/ir/pcc.rs b/cranelift/codegen/src/ir/pcc.rs
index 4f3dc4d13..33a515cac 100644
--- a/cranelift/codegen/src/ir/pcc.rs
+++ b/cranelift/codegen/src/ir/pcc.rs
@@ -1655,7 +1655,7 @@ pub fn check_vcode_facts<B: LowerBackend + TargetIsa>(
         for inst in vcode.block_insns(block).iter() {
             // Check any output facts on this inst.
             if let Err(e) = backend.check_fact(&ctx, vcode, inst, &mut flow_state) {
-                log::error!("Error checking instruction: {:?}", vcode[inst]);
+                log::info!("Error checking instruction: {:?}", vcode[inst]);
                 return Err(e);
             }
 
diff --git a/cranelift/codegen/src/isa/aarch64/lower/isle.rs b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
index d3fa587be..a70e895fb 100644
--- a/cranelift/codegen/src/isa/aarch64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
@@ -13,12 +13,12 @@ use super::{
     MachLabel, MemLabel, MoveWideConst, MoveWideOp, Opcode, OperandSize, Reg, SImm9, ScalarSize,
     ShiftOpAndAmt, UImm12Scaled, UImm5, VecMisc2, VectorSize, NZCV,
 };
-use crate::ir::condcodes;
+use crate::ir::{condcodes, ArgumentExtension};
 use crate::isa;
 use crate::isa::aarch64::inst::{FPULeftShiftImm, FPURightShiftImm, ReturnCallInfo};
 use crate::isa::aarch64::AArch64Backend;
+use crate::machinst::isle::*;
 use crate::machinst::valueregs;
-use crate::machinst::{isle::*, InputSourceInst};
 use crate::{
     binemit::CodeOffset,
     ir::{
@@ -28,7 +28,6 @@ use crate::{
     isa::aarch64::abi::AArch64CallSite,
     isa::aarch64::inst::args::{ShiftOp, ShiftOpShiftImm},
     isa::aarch64::inst::SImm7Scaled,
-    isa::unwind::UnwindInst,
     machinst::{
         abi::ArgPair, ty_bits, InstOutput, Lower, MachInst, VCodeConstant, VCodeConstantData,
     },
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 875b53561..86b2a4ffa 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -5,7 +5,7 @@ use crate::binemit::{Addend, CodeOffset, Reloc};
 pub use crate::ir::condcodes::IntCC;
 use crate::ir::types::{self, F32, F64, I128, I16, I32, I64, I8, I8X16, R32, R64};
 
-pub use crate::ir::{ExternalName, MemFlags, Opcode, SourceLoc, Type, ValueLabel};
+pub use crate::ir::{ExternalName, MemFlags, Opcode, Type};
 use crate::isa::{CallConv, FunctionAlignment};
 use crate::machinst::*;
 use crate::{settings, CodegenError, CodegenResult};
diff --git a/cranelift/codegen/src/isa/s390x/lower/isle.rs b/cranelift/codegen/src/isa/s390x/lower/isle.rs
index cec975d06..6c035f159 100644
--- a/cranelift/codegen/src/isa/s390x/lower/isle.rs
+++ b/cranelift/codegen/src/isa/s390x/lower/isle.rs
@@ -16,10 +16,10 @@ use crate::machinst::isle::*;
 use crate::machinst::{MachLabel, Reg};
 use crate::{
     ir::{
-        condcodes::*, immediates::*, types::*, ArgumentPurpose, AtomicRmwOp, BlockCall, Endianness,
-        Inst, InstructionData, KnownSymbol, LibCall, MemFlags, Opcode, TrapCode, Value, ValueList,
+        condcodes::*, immediates::*, types::*, ArgumentExtension, ArgumentPurpose, AtomicRmwOp,
+        BlockCall, Endianness, Inst, InstructionData, KnownSymbol, LibCall, MemFlags, Opcode,
+        TrapCode, Value, ValueList,
     },
-    isa::unwind::UnwindInst,
     isa::CallConv,
     machinst::abi::ABIMachineSpec,
     machinst::{
diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs
index 5908ad58a..474484c71 100644
--- a/cranelift/codegen/src/isa/x64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/x64/lower/isle.rs
@@ -3,10 +3,7 @@
 // Pull in the ISLE generated code.
 pub(crate) mod generated_code;
 use crate::{
-    ir::types,
-    ir::AtomicRmwOp,
-    isa, isle_common_prelude_methods, isle_lower_prelude_methods,
-    machinst::{InputSourceInst, Reg, Writable},
+    ir::types, ir::AtomicRmwOp, isa, isle_common_prelude_methods, isle_lower_prelude_methods,
 };
 use generated_code::{Context, MInst, RegisterClass};
 
@@ -22,12 +19,9 @@ use crate::{
         types::*,
         BlockCall, Inst, InstructionData, MemFlags, Opcode, TrapCode, Value, ValueList,
     },
-    isa::{
-        unwind::UnwindInst,
-        x64::{
-            abi::X64CallSite,
-            inst::{args::*, regs, CallInfo, ReturnCallInfo},
-        },
+    isa::x64::{
+        abi::X64CallSite,
+        inst::{args::*, regs, CallInfo, ReturnCallInfo},
     },
     machinst::{
         isle::*, valueregs, ArgPair, InsnInput, InstOutput, Lower, MachAtomicRmwOp, MachInst,
diff --git a/cranelift/codegen/src/isa/x64/pcc.rs b/cranelift/codegen/src/isa/x64/pcc.rs
index 2e367c8f9..97afc5746 100644
--- a/cranelift/codegen/src/isa/x64/pcc.rs
+++ b/cranelift/codegen/src/isa/x64/pcc.rs
@@ -3,6 +3,7 @@
 use crate::ir::pcc::*;
 use crate::ir::types::*;
 use crate::ir::Type;
+use crate::isa::x64::args::AvxOpcode;
 use crate::isa::x64::inst::args::{
     AluRmiROpcode, Amode, Gpr, Imm8Reg, RegMem, RegMemImm, ShiftKind, SyntheticAmode,
     ToWritableReg, CC,
@@ -533,38 +534,69 @@ pub(crate) fn check(
             ensure_no_fact(vcode, dst.to_writable_reg().to_reg())
         }
 
+        // NOTE: it's assumed that all of these cases perform 128-bit loads, but this hasn't been
+        // verified. The effect of this will be spurious PCC failures when these instructions are
+        // involved.
         Inst::XmmRmRUnaligned { dst, ref src2, .. }
-        | Inst::XmmRmRImmVex { dst, ref src2, .. }
-        | Inst::XmmRmRVex3 {
-            dst,
-            src3: ref src2,
-            ..
-        }
-        | Inst::XmmRmRBlendVex { dst, ref src2, .. }
-        | Inst::XmmUnaryRmRVex {
+        | Inst::XmmRmREvex { dst, ref src2, .. }
+        | Inst::XmmUnaryRmRImmEvex {
             dst, src: ref src2, ..
         }
-        | Inst::XmmUnaryRmRImmVex {
+        | Inst::XmmUnaryRmRUnaligned {
             dst, src: ref src2, ..
         }
-        | Inst::XmmRmREvex { dst, ref src2, .. }
-        | Inst::XmmUnaryRmRImmEvex {
+        | Inst::XmmUnaryRmREvex {
             dst, src: ref src2, ..
         }
         | Inst::XmmRmREvex3 {
             dst,
             src3: ref src2,
             ..
+        } => {
+            match <&RegMem>::from(src2) {
+                RegMem::Mem { ref addr } => {
+                    check_load(ctx, None, addr, vcode, I8X16, 128)?;
+                }
+                RegMem::Reg { .. } => {}
+            }
+            ensure_no_fact(vcode, dst.to_writable_reg().to_reg())
+        }
+
+        Inst::XmmRmRImmVex {
+            op, dst, ref src2, ..
+        }
+        | Inst::XmmRmRVex3 {
+            op,
+            dst,
+            src3: ref src2,
+            ..
         }
-        | Inst::XmmUnaryRmRUnaligned {
-            dst, src: ref src2, ..
+        | Inst::XmmRmRBlendVex {
+            op, dst, ref src2, ..
         }
-        | Inst::XmmUnaryRmREvex {
-            dst, src: ref src2, ..
+        | Inst::XmmUnaryRmRVex {
+            op,
+            dst,
+            src: ref src2,
+            ..
+        }
+        | Inst::XmmUnaryRmRImmVex {
+            op,
+            dst,
+            src: ref src2,
+            ..
         } => {
+            let size = match op {
+                AvxOpcode::Vmovss => 32,
+                AvxOpcode::Vmovsd => 64,
+
+                // We assume all other operations happen on 128-bit values.
+                _ => 128,
+            };
+
             match <&RegMem>::from(src2) {
                 RegMem::Mem { ref addr } => {
-                    check_load(ctx, None, addr, vcode, I8X16, 128)?;
+                    check_load(ctx, None, addr, vcode, I8X16, size)?;
                 }
                 RegMem::Reg { .. } => {}
             }
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index 1fb41f9c7..7add8208e 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -7,16 +7,15 @@ use std::cell::Cell;
 pub use super::MachLabel;
 use super::RetPair;
 pub use crate::ir::{
-    condcodes, condcodes::CondCode, dynamic_to_fixed, ArgumentExtension, ArgumentPurpose, Constant,
-    DynamicStackSlot, ExternalName, FuncRef, GlobalValue, Immediate, SigRef, StackSlot,
+    condcodes, condcodes::CondCode, dynamic_to_fixed, Constant, DynamicStackSlot, ExternalName,
+    FuncRef, GlobalValue, Immediate, SigRef, StackSlot,
 };
-pub use crate::isa::unwind::UnwindInst;
-pub use crate::isa::TargetIsa;
+pub use crate::isa::{unwind::UnwindInst, TargetIsa};
 pub use crate::machinst::{
     ABIArg, ABIArgSlot, InputSourceInst, Lower, LowerBackend, RealReg, Reg, RelocDistance, Sig,
     VCodeInst, Writable,
 };
-pub use crate::settings::{OptLevel, TlsModel};
+pub use crate::settings::TlsModel;
 
 pub type Unit = ();
 pub type ValueSlice = (ValueList, usize);
diff --git a/cranelift/codegen/src/opts.rs b/cranelift/codegen/src/opts.rs
index 3a78cac5c..00f350c6e 100644
--- a/cranelift/codegen/src/opts.rs
+++ b/cranelift/codegen/src/opts.rs
@@ -4,12 +4,11 @@ use crate::egraph::{NewOrExistingInst, OptimizeCtx};
 use crate::ir::condcodes;
 pub use crate::ir::condcodes::{FloatCC, IntCC};
 use crate::ir::dfg::ValueDef;
-pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64, Uimm8, V128Imm};
+pub use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm8, V128Imm};
 pub use crate::ir::types::*;
 pub use crate::ir::{
-    dynamic_to_fixed, AtomicRmwOp, Block, BlockCall, Constant, DataFlowGraph, DynamicStackSlot,
-    FuncRef, GlobalValue, Immediate, InstructionData, JumpTable, MemFlags, Opcode, StackSlot,
-    Table, TrapCode, Type, Value,
+    dynamic_to_fixed, AtomicRmwOp, BlockCall, Constant, DynamicStackSlot, FuncRef, GlobalValue,
+    Immediate, InstructionData, MemFlags, Opcode, StackSlot, Table, TrapCode, Type, Value,
 };
 use crate::isle_common_prelude_methods;
 use crate::machinst::isle::*;
diff --git a/cranelift/codegen/src/opts/arithmetic.isle b/cranelift/codegen/src/opts/arithmetic.isle
index 00c56c240..6553b8674 100644
--- a/cranelift/codegen/src/opts/arithmetic.isle
+++ b/cranelift/codegen/src/opts/arithmetic.isle
@@ -126,3 +126,41 @@
 ;; both of them since they cancel out.
 (rule (simplify (fmul ty (fneg ty x) (fneg ty y)))
       (fmul ty x y))
+
+;; (a op (b op (c op d))) ==> ((a op b) op (c op d))
+;;
+;; and
+;;
+;; (((a op b) op c) op d) ==> ((a op b) op (c op d))
+;;
+;; where `op` is an associative operation: `iadd`, `imul`, `band`, or `bxor`.
+;;
+;; This increases instruction-level parallelism and shrinks live ranges. It also
+;; canonicalizes into the shallow-and-wide form for reassociating constants
+;; together for cprop.
+;;
+;; NB: We subsume to avoid exponential e-node blow up due to reassociating very
+;; large chains of operations.
+;;
+;; TODO: We should add `bor` rules for this as well. Unfortunately, they
+;; conflict with our `bswap` recognizing rules when we `subsume`.
+
+(rule (simplify (iadd ty a (iadd ty b (iadd ty c d))))
+      (subsume (iadd ty (iadd ty a b) (iadd ty c d))))
+(rule (simplify (iadd ty (iadd ty (iadd ty a b) c) d))
+      (subsume (iadd ty (iadd ty a b) (iadd ty c d))))
+
+(rule (simplify (imul ty a (imul ty b (imul ty c d))))
+      (subsume (imul ty (imul ty a b) (imul ty c d))))
+(rule (simplify (imul ty (imul ty (imul ty a b) c) d))
+      (subsume (imul ty (imul ty a b) (imul ty c d))))
+
+(rule (simplify (band ty a (band ty b (band ty c d))))
+      (subsume (band ty (band ty a b) (band ty c d))))
+(rule (simplify (band ty (band ty (band ty a b) c) d))
+      (subsume (band ty (band ty a b) (band ty c d))))
+
+(rule (simplify (bxor ty a (bxor ty b (bxor ty c d))))
+      (subsume (bxor ty (bxor ty a b) (bxor ty c d))))
+(rule (simplify (bxor ty (bxor ty (bxor ty a b) c) d))
+      (subsume (bxor ty (bxor ty a b) (bxor ty c d))))
diff --git a/cranelift/codegen/src/opts/cprop.isle b/cranelift/codegen/src/opts/cprop.isle
index 26580a7ea..e44404e05 100644
--- a/cranelift/codegen/src/opts/cprop.isle
+++ b/cranelift/codegen/src/opts/cprop.isle
@@ -208,3 +208,30 @@
       (ushr ty (ushr ty a c) b))
 (rule (simplify (sshr ty (sshr ty a@(iconst _ _) b) c@(iconst _ _)))
       (sshr ty (sshr ty a c) b))
+
+;; When we operations that are both commutative and associative, reassociate
+;; constants together for cprop:
+;;
+;; ((a op B) op (c op D)) ==> ((a op c) op (B op D))
+;;
+;; Where `op` is one of: `iadd`, `imul`, `band`, `bor`, or `bxor`.
+(rule (simplify (iadd ty
+                      (iadd ty a b@(iconst _ _))
+                      (iadd ty c d@(iconst _ _))))
+      (iadd ty (iadd ty a c) (iadd ty b d)))
+(rule (simplify (imul ty
+                      (imul ty a b@(iconst _ _))
+                      (imul ty c d@(iconst _ _))))
+      (imul ty (imul ty a c) (imul ty b d)))
+(rule (simplify (band ty
+                      (band ty a b@(iconst _ _))
+                      (band ty c d@(iconst _ _))))
+      (band ty (band ty a c) (band ty b d)))
+(rule (simplify (bor ty
+                      (bor ty a b@(iconst _ _))
+                      (bor ty c d@(iconst _ _))))
+      (bor ty (bor ty a c) (bor ty b d)))
+(rule (simplify (bxor ty
+                      (bxor ty a b@(iconst _ _))
+                      (bxor ty c d@(iconst _ _))))
+      (bxor ty (bxor ty a c) (bxor ty b d)))
diff --git a/cranelift/codegen/src/unionfind.rs b/cranelift/codegen/src/unionfind.rs
index a51f305c2..8e2be3ba3 100644
--- a/cranelift/codegen/src/unionfind.rs
+++ b/cranelift/codegen/src/unionfind.rs
@@ -13,6 +13,7 @@ pub struct UnionFind<Idx: EntityRef> {
 
 #[derive(Clone, Debug, PartialEq)]
 struct Val<Idx>(Idx);
+
 impl<Idx: EntityRef + ReservedValue> Default for Val<Idx> {
     fn default() -> Self {
         Self(Idx::reserved_value())
diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs
index a997846c1..279831259 100644
--- a/crates/cache/src/lib.rs
+++ b/crates/cache/src/lib.rs
@@ -2,11 +2,11 @@ use base64::Engine;
 use log::{debug, trace, warn};
 use serde::{Deserialize, Serialize};
 use sha2::{Digest, Sha256};
-use std::fs;
 use std::hash::Hash;
 use std::hash::Hasher;
 use std::io::Write;
 use std::path::{Path, PathBuf};
+use std::{fs, io};
 
 #[macro_use] // for tests
 mod config;
@@ -172,7 +172,7 @@ impl<'config> ModuleCacheEntryInner<'config> {
 
         // Optimize syscalls: first, try writing to disk. It should succeed in most cases.
         // Otherwise, try creating the cache directory and retry writing to the file.
-        if fs_write_atomic(&mod_cache_path, "mod", &compressed_data) {
+        if fs_write_atomic(&mod_cache_path, "mod", &compressed_data).is_ok() {
             return Some(());
         }
 
@@ -193,10 +193,16 @@ impl<'config> ModuleCacheEntryInner<'config> {
             })
             .ok()?;
 
-        if fs_write_atomic(&mod_cache_path, "mod", &compressed_data) {
-            Some(())
-        } else {
-            None
+        match fs_write_atomic(&mod_cache_path, "mod", &compressed_data) {
+            Ok(_) => Some(()),
+            Err(err) => {
+                warn!(
+                    "Failed to write file with rename, target path: {}, err: {}",
+                    mod_cache_path.display(),
+                    err
+                );
+                None
+            }
         }
     }
 }
@@ -214,7 +220,7 @@ impl Hasher for Sha256Hasher {
 // Assumption: path inside cache directory.
 // Then, we don't have to use sound OS-specific exclusive file access.
 // Note: there's no need to remove temporary file here - cleanup task will do it later.
-fn fs_write_atomic(path: &Path, reason: &str, contents: &[u8]) -> bool {
+fn fs_write_atomic(path: &Path, reason: &str, contents: &[u8]) -> io::Result<()> {
     let lock_path = path.with_extension(format!("wip-atomic-write-{}", reason));
     fs::OpenOptions::new()
         .create_new(true) // atomic file creation (assumption: no one will open it without this flag)
@@ -223,15 +229,6 @@ fn fs_write_atomic(path: &Path, reason: &str, contents: &[u8]) -> bool {
         .and_then(|mut file| file.write_all(contents))
         // file should go out of scope and be closed at this point
         .and_then(|()| fs::rename(&lock_path, &path)) // atomic file rename
-        .map_err(|err| {
-            warn!(
-                "Failed to write file with rename, lock path: {}, target path: {}, err: {}",
-                lock_path.display(),
-                path.display(),
-                err
-            )
-        })
-        .is_ok()
 }
 
 #[cfg(test)]
diff --git a/crates/cache/src/worker.rs b/crates/cache/src/worker.rs
index 72a53f94c..64eff12f3 100644
--- a/crates/cache/src/worker.rs
+++ b/crates/cache/src/worker.rs
@@ -758,11 +758,7 @@ fn write_stats_file(path: &Path, stats: &ModuleCacheStatistics) -> bool {
             )
         })
         .and_then(|serialized| {
-            if fs_write_atomic(path, "stats", serialized.as_bytes()) {
-                Ok(())
-            } else {
-                Err(())
-            }
+            fs_write_atomic(path, "stats", serialized.as_bytes()).map_err(|_| ())
         })
         .is_ok()
 }
diff --git a/crates/cranelift/src/debug.rs b/crates/cranelift/src/debug.rs
index 04095d640..1b10eb351 100644
--- a/crates/cranelift/src/debug.rs
+++ b/crates/cranelift/src/debug.rs
@@ -11,7 +11,7 @@ pub enum ModuleMemoryOffset {
     Imported(u32),
 }
 
-pub use write_debuginfo::{emit_dwarf, DwarfSection, DwarfSectionRelocTarget};
+pub use write_debuginfo::{emit_dwarf, DwarfSectionRelocTarget};
 
 mod gc;
 mod transform;
diff --git a/crates/runtime/src/mpk/pkru.rs b/crates/runtime/src/mpk/pkru.rs
index 9e36dd35a..ea9689686 100644
--- a/crates/runtime/src/mpk/pkru.rs
+++ b/crates/runtime/src/mpk/pkru.rs
@@ -53,16 +53,24 @@ pub fn write(pkru: u32) {
     }
 }
 
-/// Check the `ECX.PKU` flag (bit 3) of the `07h` `CPUID` leaf; see the
-/// Intel Software Development Manual, vol 3a, section 2.7.
+/// Check the `ECX.PKU` flag (bit 3) of the `07h` `CPUID` leaf; see the Intel
+/// Software Development Manual, vol 3a, section 2.7. This flag is only set on
+/// Intel CPUs, so this function also checks the `CPUID` vendor string.
 pub fn has_cpuid_bit_set() -> bool {
-    // TODO: disable MPK support until the following issue is resolved:
-    // https://github.com/bytecodealliance/wasmtime/issues/7445
-    if true {
-        return false;
-    }
     let result = unsafe { std::arch::x86_64::__cpuid(0x07) };
-    (result.ecx & 0b100) != 0
+    is_intel_cpu() && (result.ecx & 0b100) != 0
+}
+
+/// Check the `CPUID` vendor string for `GenuineIntel`; see the Intel Software
+/// Development Manual, vol 2a, `CPUID` description.
+pub fn is_intel_cpu() -> bool {
+    // To read the CPU vendor string, we pass 0 in EAX and read 12 ASCII bytes
+    // from EBX, EDX, and ECX (in that order).
+    let result = unsafe { std::arch::x86_64::__cpuid(0) };
+    // Then we check if the vendor string matches "GenuineIntel".
+    result.ebx == u32::from_le_bytes(*b"Genu")
+        && result.edx == u32::from_le_bytes(*b"ineI")
+        && result.ecx == u32::from_le_bytes(*b"ntel")
 }
 
 #[cfg(test)]
diff --git a/crates/runtime/src/traphandlers/unix.rs b/crates/runtime/src/traphandlers/unix.rs
index 613119af7..760295d70 100644
--- a/crates/runtime/src/traphandlers/unix.rs
+++ b/crates/runtime/src/traphandlers/unix.rs
@@ -173,21 +173,6 @@ unsafe extern "C" fn trap_handler(
     }
 }
 
-// FIXME(rust-lang/libc#3312) the currenty definition of `ucontext_t` in the
-// `libc` crate is incorrect on aarch64-apple-drawin so it's defined here with a
-// more accurate definition. When that PR and/or issue is resolved then this
-// should no longer be necessary.
-#[repr(C)]
-#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
-struct ucontext_t {
-    uc_onstack: libc::c_int,
-    uc_sigmask: libc::sigset_t,
-    uc_stack: libc::stack_t,
-    uc_link: *mut libc::ucontext_t,
-    uc_mcsize: usize,
-    uc_mcontext: libc::mcontext_t,
-}
-
 unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const u8, usize) {
     cfg_if::cfg_if! {
         if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "x86_64"))] {
@@ -229,7 +214,7 @@ unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const
                 (*cx.uc_mcontext).__ss.__rbp as usize,
             )
         } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
-            let cx = &*(cx as *const ucontext_t);
+            let cx = &*(cx as *const libc::ucontext_t);
             (
                 (*cx.uc_mcontext).__ss.__pc as *const u8,
                 (*cx.uc_mcontext).__ss.__fp as usize,
@@ -285,7 +270,7 @@ unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
                 (*cx.uc_mcontext).__ss.__rsp -= 8;
             }
         } else if #[cfg(target_arch = "aarch64")] {
-            let cx = &mut *(cx as *mut ucontext_t);
+            let cx = &mut *(cx as *mut libc::ucontext_t);
             (*cx.uc_mcontext).__ss.__pc = pc as u64;
             (*cx.uc_mcontext).__ss.__x[0] = arg1 as u64;
         } else {
diff --git a/crates/wasi-http/wit/deps/filesystem/types.wit b/crates/wasi-http/wit/deps/filesystem/types.wit
index 37700332b..4ab5dc055 100644
--- a/crates/wasi-http/wit/deps/filesystem/types.wit
+++ b/crates/wasi-http/wit/deps/filesystem/types.wit
@@ -142,29 +142,6 @@ interface types {
         truncate,
     }
 
-    /// Permissions mode used by `open-at`, `change-file-permissions-at`, and
-    /// similar.
-    flags modes {
-        /// True if the resource is considered readable by the containing
-        /// filesystem.
-        readable,
-        /// True if the resource is considered writable by the containing
-        /// filesystem.
-        writable,
-        /// True if the resource is considered executable by the containing
-        /// filesystem. This does not apply to directories.
-        executable,
-    }
-
-    /// Access type used by `access-at`.
-    variant access-type {
-        /// Test for readability, writeability, or executability.
-        access(modes),
-
-        /// Test whether the path exists.
-        exists,
-    }
-
     /// Number of hard links to an inode.
     type link-count = u64;
 
@@ -538,8 +515,6 @@ interface types {
             open-flags: open-flags,
             /// Flags to use for the resulting descriptor.
             %flags: descriptor-flags,
-            /// Permissions to use when creating a new file.
-            modes: modes
         ) -> result<descriptor, error-code>;
 
         /// Read the contents of a symbolic link.
@@ -588,25 +563,6 @@ interface types {
             new-path: string,
         ) -> result<_, error-code>;
 
-        /// Check accessibility of a filesystem path.
-        ///
-        /// Check whether the given filesystem path names an object which is
-        /// readable, writable, or executable, or whether it exists.
-        ///
-        /// This does not a guarantee that subsequent accesses will succeed, as
-        /// filesystem permissions may be modified asynchronously by external
-        /// entities.
-        ///
-        /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX.
-        access-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to check.
-            path: string,
-            /// The type of check to perform.
-            %type: access-type
-        ) -> result<_, error-code>;
-
         /// Unlink a filesystem object that is not a directory.
         ///
         /// Return `error-code::is-directory` if the path refers to a directory.
@@ -616,40 +572,6 @@ interface types {
             path: string,
         ) -> result<_, error-code>;
 
-        /// Change the permissions of a filesystem object that is not a directory.
-        ///
-        /// Note that the ultimate meanings of these permissions is
-        /// filesystem-specific.
-        ///
-        /// Note: This is similar to `fchmodat` in POSIX.
-        change-file-permissions-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to operate on.
-            path: string,
-            /// The new permissions for the filesystem object.
-            modes: modes,
-        ) -> result<_, error-code>;
-
-        /// Change the permissions of a directory.
-        ///
-        /// Note that the ultimate meanings of these permissions is
-        /// filesystem-specific.
-        ///
-        /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search"
-        /// flag. `read` on a directory implies readability and searchability, and
-        /// `execute` is not valid for directories.
-        ///
-        /// Note: This is similar to `fchmodat` in POSIX.
-        change-directory-permissions-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to operate on.
-            path: string,
-            /// The new permissions for the directory.
-            modes: modes,
-        ) -> result<_, error-code>;
-
         /// Test whether two descriptors refer to the same filesystem object.
         ///
         /// In POSIX, this corresponds to testing whether the two descriptors have the
diff --git a/crates/wasi-http/wit/deps/sockets/tcp.wit b/crates/wasi-http/wit/deps/sockets/tcp.wit
index b9ef0625a..f0d875095 100644
--- a/crates/wasi-http/wit/deps/sockets/tcp.wit
+++ b/crates/wasi-http/wit/deps/sockets/tcp.wit
@@ -2,6 +2,7 @@
 interface tcp {
     use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream};
     use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
+    use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05.{duration};
     use network.{network, error-code, ip-socket-address, ip-address-family};
 
     enum shutdown-type {
@@ -125,8 +126,11 @@ interface tcp {
         /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket:
         /// - `address-family`
         /// - `ipv6-only`
-        /// - `keep-alive`
-        /// - `unicast-hop-limit`
+        /// - `keep-alive-enabled`
+        /// - `keep-alive-idle-time`
+        /// - `keep-alive-interval`
+        /// - `keep-alive-count`
+        /// - `hop-limit`
         /// - `receive-buffer-size`
         /// - `send-buffer-size`
         ///
@@ -176,6 +180,11 @@ interface tcp {
         /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
         remote-address: func() -> result<ip-socket-address, error-code>;
 
+        /// Whether the socket is listening for new connections.
+        ///
+        /// Equivalent to the SO_ACCEPTCONN socket option.
+        is-listening: func() -> bool;
+
         /// Whether this is a IPv4 or IPv6 socket.
         ///
         /// Equivalent to the SO_DOMAIN socket option.
@@ -194,36 +203,87 @@ interface tcp {
 
         /// Hints the desired listen queue size. Implementations are free to ignore this.
         ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        ///
         /// # Typical errors
         /// - `not-supported`:        (set) The platform does not support changing the backlog size after the initial listen.
+        /// - `invalid-argument`:     (set) The provided value was 0.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         set-listen-backlog-size: func(value: u64) -> result<_, error-code>;
 
+        /// Enables or disables keepalive.
+        ///
+        /// The keepalive behavior can be adjusted using:
+        /// - `keep-alive-idle-time`
+        /// - `keep-alive-interval`
+        /// - `keep-alive-count`
+        /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true.
+        ///
         /// Equivalent to the SO_KEEPALIVE socket option.
-        keep-alive: func() -> result<bool, error-code>;
-        set-keep-alive: func(value: bool) -> result<_, error-code>;
+        keep-alive-enabled: func() -> result<bool, error-code>;
+        set-keep-alive-enabled: func(value: bool) -> result<_, error-code>;
+
+        /// Amount of time the connection has to be idle before TCP starts sending keepalive packets.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS)
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-idle-time: func() -> result<duration, error-code>;
+        set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>;
+
+        /// The time between keepalive packets.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPINTVL socket option.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-interval: func() -> result<duration, error-code>;
+        set-keep-alive-interval: func(value: duration) -> result<_, error-code>;
+
+        /// The maximum amount of keepalive packets TCP should send before aborting the connection.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPCNT socket option.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-count: func() -> result<u32, error-code>;
+        set-keep-alive-count: func(value: u32) -> result<_, error-code>;
 
         /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
         ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        ///
         /// # Typical errors
         /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         /// - `invalid-state`:        (set) The socket is already in the Listener state.
-        unicast-hop-limit: func() -> result<u8, error-code>;
-        set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
+        hop-limit: func() -> result<u8, error-code>;
+        set-hop-limit: func(value: u8) -> result<_, error-code>;
 
         /// The kernel buffer space reserved for sends/receives on this socket.
         ///
-        /// Note #1: an implementation may choose to cap or round the buffer size when setting the value.
-        ///     In other words, after setting a value, reading the same setting back may return a different value.
-        ///
-        /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of
-        ///     actual data to be sent/received by the application, because the kernel might also use the buffer space
-        ///     for internal metadata structures.
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
         ///
         /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
         ///
         /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         /// - `invalid-state`:        (set) The socket is already in the Listener state.
         receive-buffer-size: func() -> result<u64, error-code>;
diff --git a/crates/wasi-http/wit/deps/sockets/udp.wit b/crates/wasi-http/wit/deps/sockets/udp.wit
index 1ee1843a9..9dc1e990b 100644
--- a/crates/wasi-http/wit/deps/sockets/udp.wit
+++ b/crates/wasi-http/wit/deps/sockets/udp.wit
@@ -154,19 +154,24 @@ interface udp {
         set-ipv6-only: func(value: bool) -> result<_, error-code>;
 
         /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
         unicast-hop-limit: func() -> result<u8, error-code>;
         set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
 
         /// The kernel buffer space reserved for sends/receives on this socket.
         ///
-        /// Note #1: an implementation may choose to cap or round the buffer size when setting the value.
-        /// 	In other words, after setting a value, reading the same setting back may return a different value.
-        ///
-        /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of
-        /// 	actual data to be sent/received by the application, because the kernel might also use the buffer space
-        /// 	for internal metadata structures.
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
         ///
         /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
         receive-buffer-size: func() -> result<u64, error-code>;
         set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
         send-buffer-size: func() -> result<u64, error-code>;
diff --git a/crates/wasi-http/wit/main.wit b/crates/wasi-http/wit/main.wit
deleted file mode 100644
index 90aa28ad3..000000000
--- a/crates/wasi-http/wit/main.wit
+++ /dev/null
@@ -1,33 +0,0 @@
-package wasmtime:wasi;
-
-// All of the same imports available in the wasi:cli/command world, but no
-// export required:
-world preview1-adapter-reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-05;
-  import wasi:sockets/network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-05;
-  import wasi:random/random@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-05;
-  import wasi:io/poll@0.2.0-rc-2023-11-05;
-  import wasi:io/streams@0.2.0-rc-2023-11-05;
-  import wasi:cli/environment@0.2.0-rc-2023-11-05;
-  import wasi:cli/exit@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-input@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-output@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stderr@0.2.0-rc-2023-11-05;
-}
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index e9f5c6f0f..3e2176516 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -45,7 +45,7 @@ pub mod bindings {
     #[cfg(feature = "reactor")]
     wit_bindgen::generate!({
         path: "../wasi/wit",
-        world: "wasmtime:wasi/preview1-adapter-reactor",
+        world: "wasi:cli/reactor",
         std_feature,
         raw_strings,
         // Automatically generated bindings for these functions will allocate
@@ -1456,7 +1456,6 @@ pub unsafe extern "C" fn path_open(
     let at_flags = at_flags_from_lookupflags(dirflags);
     let o_flags = o_flags_from_oflags(oflags);
     let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);
-    let mode = filesystem::Modes::READABLE | filesystem::Modes::WRITABLE;
     let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND;
 
     State::with(|state| {
@@ -1464,7 +1463,7 @@ pub unsafe extern "C" fn path_open(
             .descriptors()
             .get_dir(fd)?
             .fd
-            .open_at(at_flags, path, o_flags, flags, mode)?;
+            .open_at(at_flags, path, o_flags, flags)?;
         let descriptor_type = result.get_type()?;
         let desc = Descriptor::Streams(Streams {
             input: OnceCell::new(),
diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs
index f011d69e2..39559ed43 100644
--- a/crates/wasi/src/preview2/host/filesystem.rs
+++ b/crates/wasi/src/preview2/host/filesystem.rs
@@ -487,9 +487,6 @@ impl<T: WasiView> HostDescriptor for T {
         path: String,
         oflags: types::OpenFlags,
         flags: types::DescriptorFlags,
-        // TODO: These are the permissions to use when creating a new file.
-        // Not implemented yet.
-        _mode: types::Modes,
     ) -> FsResult<Resource<types::Descriptor>> {
         use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
         use system_interface::fs::{FdFlags, GetSetFdFlags};
@@ -698,36 +695,6 @@ impl<T: WasiView> HostDescriptor for T {
             .await?)
     }
 
-    async fn access_at(
-        &mut self,
-        _fd: Resource<types::Descriptor>,
-        _path_flags: types::PathFlags,
-        _path: String,
-        _access: types::AccessType,
-    ) -> FsResult<()> {
-        todo!("filesystem access_at is not implemented")
-    }
-
-    async fn change_file_permissions_at(
-        &mut self,
-        _fd: Resource<types::Descriptor>,
-        _path_flags: types::PathFlags,
-        _path: String,
-        _mode: types::Modes,
-    ) -> FsResult<()> {
-        todo!("filesystem change_file_permissions_at is not implemented")
-    }
-
-    async fn change_directory_permissions_at(
-        &mut self,
-        _fd: Resource<types::Descriptor>,
-        _path_flags: types::PathFlags,
-        _path: String,
-        _mode: types::Modes,
-    ) -> FsResult<()> {
-        todo!("filesystem change_directory_permissions_at is not implemented")
-    }
-
     fn read_via_stream(
         &mut self,
         fd: Resource<types::Descriptor>,
diff --git a/crates/wasi/src/preview2/host/filesystem/sync.rs b/crates/wasi/src/preview2/host/filesystem/sync.rs
index 28a9b77f5..3b3d83e9d 100644
--- a/crates/wasi/src/preview2/host/filesystem/sync.rs
+++ b/crates/wasi/src/preview2/host/filesystem/sync.rs
@@ -175,7 +175,6 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
         path: String,
         oflags: sync_filesystem::OpenFlags,
         flags: sync_filesystem::DescriptorFlags,
-        mode: sync_filesystem::Modes,
     ) -> FsResult<Resource<sync_filesystem::Descriptor>> {
         in_tokio(async {
             async_filesystem::HostDescriptor::open_at(
@@ -185,7 +184,6 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
                 path,
                 oflags.into(),
                 flags.into(),
-                mode.into(),
             )
             .await
         })
@@ -244,63 +242,6 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
         in_tokio(async { async_filesystem::HostDescriptor::unlink_file_at(self, fd, path).await })
     }
 
-    fn access_at(
-        &mut self,
-        fd: Resource<sync_filesystem::Descriptor>,
-        path_flags: sync_filesystem::PathFlags,
-        path: String,
-        access: sync_filesystem::AccessType,
-    ) -> FsResult<()> {
-        in_tokio(async {
-            async_filesystem::HostDescriptor::access_at(
-                self,
-                fd,
-                path_flags.into(),
-                path,
-                access.into(),
-            )
-            .await
-        })
-    }
-
-    fn change_file_permissions_at(
-        &mut self,
-        fd: Resource<sync_filesystem::Descriptor>,
-        path_flags: sync_filesystem::PathFlags,
-        path: String,
-        mode: sync_filesystem::Modes,
-    ) -> FsResult<()> {
-        in_tokio(async {
-            async_filesystem::HostDescriptor::change_file_permissions_at(
-                self,
-                fd,
-                path_flags.into(),
-                path,
-                mode.into(),
-            )
-            .await
-        })
-    }
-
-    fn change_directory_permissions_at(
-        &mut self,
-        fd: Resource<sync_filesystem::Descriptor>,
-        path_flags: sync_filesystem::PathFlags,
-        path: String,
-        mode: sync_filesystem::Modes,
-    ) -> FsResult<()> {
-        in_tokio(async {
-            async_filesystem::HostDescriptor::change_directory_permissions_at(
-                self,
-                fd,
-                path_flags.into(),
-                path,
-                mode.into(),
-            )
-            .await
-        })
-    }
-
     fn read_via_stream(
         &mut self,
         fd: Resource<sync_filesystem::Descriptor>,
@@ -566,30 +507,6 @@ impl From<sync_filesystem::DescriptorFlags> for async_filesystem::DescriptorFlag
         out
     }
 }
-impl From<sync_filesystem::Modes> for async_filesystem::Modes {
-    fn from(other: sync_filesystem::Modes) -> Self {
-        let mut out = Self::empty();
-        if other.contains(sync_filesystem::Modes::READABLE) {
-            out |= Self::READABLE;
-        }
-        if other.contains(sync_filesystem::Modes::WRITABLE) {
-            out |= Self::WRITABLE;
-        }
-        if other.contains(sync_filesystem::Modes::EXECUTABLE) {
-            out |= Self::EXECUTABLE;
-        }
-        out
-    }
-}
-impl From<sync_filesystem::AccessType> for async_filesystem::AccessType {
-    fn from(other: sync_filesystem::AccessType) -> Self {
-        use sync_filesystem::AccessType;
-        match other {
-            AccessType::Access(modes) => Self::Access(modes.into()),
-            AccessType::Exists => Self::Exists,
-        }
-    }
-}
 impl From<async_filesystem::MetadataHashValue> for sync_filesystem::MetadataHashValue {
     fn from(other: async_filesystem::MetadataHashValue) -> Self {
         Self {
diff --git a/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs
index ce6f2f155..ad8d7ed7b 100644
--- a/crates/wasi/src/preview2/host/network.rs
+++ b/crates/wasi/src/preview2/host/network.rs
@@ -213,6 +213,7 @@ impl From<cap_net_ext::AddressFamily> for IpAddressFamily {
 
 pub(crate) mod util {
     use std::net::{IpAddr, Ipv6Addr, SocketAddr};
+    use std::time::Duration;
 
     use crate::preview2::bindings::sockets::network::ErrorCode;
     use crate::preview2::network::SocketAddressFamily;
@@ -371,6 +372,52 @@ pub(crate) mod util {
         }
     }
 
+    pub fn set_tcp_keepidle<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
+        if value <= Duration::ZERO {
+            return Err(Errno::INVAL);
+        }
+
+        // Ensure that the value passed to the actual syscall never gets rounded down to 0.
+        const MIN_SECS: u64 = 1;
+
+        // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
+        const MAX_SECS: u64 = i16::MAX as u64;
+
+        sockopt::set_tcp_keepidle(
+            sockfd,
+            value.clamp(Duration::from_secs(MIN_SECS), Duration::from_secs(MAX_SECS)),
+        )
+    }
+
+    pub fn set_tcp_keepintvl<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
+        if value <= Duration::ZERO {
+            return Err(Errno::INVAL);
+        }
+
+        // Ensure that any fractional value passed to the actual syscall never gets rounded down to 0.
+        const MIN_SECS: u64 = 1;
+
+        // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
+        const MAX_SECS: u64 = i16::MAX as u64;
+
+        sockopt::set_tcp_keepintvl(
+            sockfd,
+            value.clamp(Duration::from_secs(MIN_SECS), Duration::from_secs(MAX_SECS)),
+        )
+    }
+
+    pub fn set_tcp_keepcnt<Fd: AsFd>(sockfd: Fd, value: u32) -> rustix::io::Result<()> {
+        if value == 0 {
+            return Err(Errno::INVAL);
+        }
+
+        const MIN_CNT: u32 = 1;
+        // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
+        const MAX_CNT: u32 = i8::MAX as u32;
+
+        sockopt::set_tcp_keepcnt(sockfd, value.clamp(MIN_CNT, MAX_CNT))
+    }
+
     pub fn get_ip_ttl<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<u8> {
         sockopt::get_ip_ttl(sockfd)?
             .try_into()
@@ -430,6 +477,10 @@ pub(crate) mod util {
         sockfd: Fd,
         value: usize,
     ) -> rustix::io::Result<()> {
+        if value == 0 {
+            return Err(Errno::INVAL);
+        }
+
         let value = normalize_set_buffer_size(value);
 
         match sockopt::set_socket_recv_buffer_size(sockfd, value) {
@@ -450,6 +501,10 @@ pub(crate) mod util {
         sockfd: Fd,
         value: usize,
     ) -> rustix::io::Result<()> {
+        if value == 0 {
+            return Err(Errno::INVAL);
+        }
+
         let value = normalize_set_buffer_size(value);
 
         match sockopt::set_socket_send_buffer_size(sockfd, value) {
diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs
index 665b2a85a..1c805467b 100644
--- a/crates/wasi/src/preview2/host/tcp.rs
+++ b/crates/wasi/src/preview2/host/tcp.rs
@@ -15,6 +15,7 @@ use io_lifetimes::AsSocketlike;
 use rustix::io::Errno;
 use rustix::net::sockopt;
 use std::net::SocketAddr;
+use std::time::Duration;
 use tokio::io::Interest;
 use wasmtime::component::Resource;
 
@@ -264,6 +265,10 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
             {
                 _ = util::set_ipv6_unicast_hops(&connection, ttl); // Ignore potential error.
             }
+
+            if let Some(value) = socket.keep_alive_idle_time {
+                _ = util::set_tcp_keepidle(&connection, value); // Ignore potential error.
+            }
         }
 
         let mut tcp_socket = TcpSocket::from_tcp_stream(connection, socket.family)?;
@@ -317,6 +322,16 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         Ok(addr.into())
     }
 
+    fn is_listening(&mut self, this: Resource<tcp::TcpSocket>) -> Result<bool, anyhow::Error> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+
+        match socket.tcp_state {
+            TcpState::Listening => Ok(true),
+            _ => Ok(false),
+        }
+    }
+
     fn address_family(
         &mut self,
         this: Resource<tcp::TcpSocket>,
@@ -335,7 +350,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         let socket = table.get(&this)?;
 
         // Instead of just calling the OS we return our own internal state, because
-        // MacOS doesn't propogate the V6ONLY state on to accepted client sockets.
+        // MacOS doesn't propagate the V6ONLY state on to accepted client sockets.
 
         match socket.family {
             SocketAddressFamily::Ipv4 => Err(ErrorCode::NotSupported.into()),
@@ -372,6 +387,10 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         let table = self.table_mut();
         let socket = table.get_mut(&this)?;
 
+        if value == 0 {
+            return Err(ErrorCode::InvalidArgument.into());
+        }
+
         // Silently clamp backlog size. This is OK for us to do, because operating systems do this too.
         let value = value
             .try_into()
@@ -403,19 +422,84 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         }
     }
 
-    fn keep_alive(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<bool> {
+    fn keep_alive_enabled(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<bool> {
         let table = self.table();
         let socket = table.get(&this)?;
         Ok(sockopt::get_socket_keepalive(socket.tcp_socket())?)
     }
 
-    fn set_keep_alive(&mut self, this: Resource<tcp::TcpSocket>, value: bool) -> SocketResult<()> {
+    fn set_keep_alive_enabled(
+        &mut self,
+        this: Resource<tcp::TcpSocket>,
+        value: bool,
+    ) -> SocketResult<()> {
         let table = self.table();
         let socket = table.get(&this)?;
         Ok(sockopt::set_socket_keepalive(socket.tcp_socket(), value)?)
     }
 
-    fn unicast_hop_limit(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u8> {
+    fn keep_alive_idle_time(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+        Ok(sockopt::get_tcp_keepidle(socket.tcp_socket())?.as_nanos() as u64)
+    }
+
+    fn set_keep_alive_idle_time(
+        &mut self,
+        this: Resource<tcp::TcpSocket>,
+        value: u64,
+    ) -> SocketResult<()> {
+        let table = self.table_mut();
+        let socket = table.get_mut(&this)?;
+
+        let duration = Duration::from_nanos(value);
+
+        util::set_tcp_keepidle(socket.tcp_socket(), duration)?;
+
+        #[cfg(target_os = "macos")]
+        {
+            socket.keep_alive_idle_time = Some(duration);
+        }
+
+        Ok(())
+    }
+
+    fn keep_alive_interval(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+        Ok(sockopt::get_tcp_keepintvl(socket.tcp_socket())?.as_nanos() as u64)
+    }
+
+    fn set_keep_alive_interval(
+        &mut self,
+        this: Resource<tcp::TcpSocket>,
+        value: u64,
+    ) -> SocketResult<()> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+        Ok(util::set_tcp_keepintvl(
+            socket.tcp_socket(),
+            Duration::from_nanos(value),
+        )?)
+    }
+
+    fn keep_alive_count(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u32> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+        Ok(sockopt::get_tcp_keepcnt(socket.tcp_socket())?)
+    }
+
+    fn set_keep_alive_count(
+        &mut self,
+        this: Resource<tcp::TcpSocket>,
+        value: u32,
+    ) -> SocketResult<()> {
+        let table = self.table();
+        let socket = table.get(&this)?;
+        Ok(util::set_tcp_keepcnt(socket.tcp_socket(), value)?)
+    }
+
+    fn hop_limit(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u8> {
         let table = self.table();
         let socket = table.get(&this)?;
 
@@ -427,11 +511,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         Ok(ttl)
     }
 
-    fn set_unicast_hop_limit(
-        &mut self,
-        this: Resource<tcp::TcpSocket>,
-        value: u8,
-    ) -> SocketResult<()> {
+    fn set_hop_limit(&mut self, this: Resource<tcp::TcpSocket>, value: u8) -> SocketResult<()> {
         let table = self.table_mut();
         let socket = table.get_mut(&this)?;
 
diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs
index cc976f1dd..7760bc418 100644
--- a/crates/wasi/src/preview2/preview1.rs
+++ b/crates/wasi/src/preview2/preview1.rs
@@ -1893,14 +1893,7 @@ impl<
         };
         drop(t);
         let fd = self
-            .open_at(
-                dirfd,
-                dirflags.into(),
-                path,
-                oflags.into(),
-                flags,
-                filesystem::Modes::READABLE | filesystem::Modes::WRITABLE,
-            )
+            .open_at(dirfd, dirflags.into(), path, oflags.into(), flags)
             .await
             .map_err(|e| {
                 e.try_into()
diff --git a/crates/wasi/src/preview2/tcp.rs b/crates/wasi/src/preview2/tcp.rs
index b6ebffe7e..23061760c 100644
--- a/crates/wasi/src/preview2/tcp.rs
+++ b/crates/wasi/src/preview2/tcp.rs
@@ -64,15 +64,17 @@ pub struct TcpSocket {
 
     pub(crate) family: SocketAddressFamily,
 
-    /// The manually configured buffer size. `None` means: no preference, use system default.
+    // The socket options below are not automatically inherited from the listener
+    // on all platforms. So we keep track of which options have been explicitly
+    // set and manually apply those values to newly accepted clients.
     #[cfg(target_os = "macos")]
     pub(crate) receive_buffer_size: Option<usize>,
-    /// The manually configured buffer size. `None` means: no preference, use system default.
     #[cfg(target_os = "macos")]
     pub(crate) send_buffer_size: Option<usize>,
-    /// The manually configured TTL. `None` means: no preference, use system default.
     #[cfg(target_os = "macos")]
     pub(crate) hop_limit: Option<u8>,
+    #[cfg(target_os = "macos")]
+    pub(crate) keep_alive_idle_time: Option<std::time::Duration>,
 }
 
 pub(crate) struct TcpReadStream {
@@ -301,6 +303,8 @@ impl TcpSocket {
             send_buffer_size: None,
             #[cfg(target_os = "macos")]
             hop_limit: None,
+            #[cfg(target_os = "macos")]
+            keep_alive_idle_time: None,
         })
     }
 
diff --git a/crates/wasi/wit/deps/filesystem/types.wit b/crates/wasi/wit/deps/filesystem/types.wit
index 37700332b..4ab5dc055 100644
--- a/crates/wasi/wit/deps/filesystem/types.wit
+++ b/crates/wasi/wit/deps/filesystem/types.wit
@@ -142,29 +142,6 @@ interface types {
         truncate,
     }
 
-    /// Permissions mode used by `open-at`, `change-file-permissions-at`, and
-    /// similar.
-    flags modes {
-        /// True if the resource is considered readable by the containing
-        /// filesystem.
-        readable,
-        /// True if the resource is considered writable by the containing
-        /// filesystem.
-        writable,
-        /// True if the resource is considered executable by the containing
-        /// filesystem. This does not apply to directories.
-        executable,
-    }
-
-    /// Access type used by `access-at`.
-    variant access-type {
-        /// Test for readability, writeability, or executability.
-        access(modes),
-
-        /// Test whether the path exists.
-        exists,
-    }
-
     /// Number of hard links to an inode.
     type link-count = u64;
 
@@ -538,8 +515,6 @@ interface types {
             open-flags: open-flags,
             /// Flags to use for the resulting descriptor.
             %flags: descriptor-flags,
-            /// Permissions to use when creating a new file.
-            modes: modes
         ) -> result<descriptor, error-code>;
 
         /// Read the contents of a symbolic link.
@@ -588,25 +563,6 @@ interface types {
             new-path: string,
         ) -> result<_, error-code>;
 
-        /// Check accessibility of a filesystem path.
-        ///
-        /// Check whether the given filesystem path names an object which is
-        /// readable, writable, or executable, or whether it exists.
-        ///
-        /// This does not a guarantee that subsequent accesses will succeed, as
-        /// filesystem permissions may be modified asynchronously by external
-        /// entities.
-        ///
-        /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX.
-        access-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to check.
-            path: string,
-            /// The type of check to perform.
-            %type: access-type
-        ) -> result<_, error-code>;
-
         /// Unlink a filesystem object that is not a directory.
         ///
         /// Return `error-code::is-directory` if the path refers to a directory.
@@ -616,40 +572,6 @@ interface types {
             path: string,
         ) -> result<_, error-code>;
 
-        /// Change the permissions of a filesystem object that is not a directory.
-        ///
-        /// Note that the ultimate meanings of these permissions is
-        /// filesystem-specific.
-        ///
-        /// Note: This is similar to `fchmodat` in POSIX.
-        change-file-permissions-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to operate on.
-            path: string,
-            /// The new permissions for the filesystem object.
-            modes: modes,
-        ) -> result<_, error-code>;
-
-        /// Change the permissions of a directory.
-        ///
-        /// Note that the ultimate meanings of these permissions is
-        /// filesystem-specific.
-        ///
-        /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search"
-        /// flag. `read` on a directory implies readability and searchability, and
-        /// `execute` is not valid for directories.
-        ///
-        /// Note: This is similar to `fchmodat` in POSIX.
-        change-directory-permissions-at: func(
-            /// Flags determining the method of how the path is resolved.
-            path-flags: path-flags,
-            /// The relative path to operate on.
-            path: string,
-            /// The new permissions for the directory.
-            modes: modes,
-        ) -> result<_, error-code>;
-
         /// Test whether two descriptors refer to the same filesystem object.
         ///
         /// In POSIX, this corresponds to testing whether the two descriptors have the
diff --git a/crates/wasi/wit/deps/sockets/tcp.wit b/crates/wasi/wit/deps/sockets/tcp.wit
index b9ef0625a..f0d875095 100644
--- a/crates/wasi/wit/deps/sockets/tcp.wit
+++ b/crates/wasi/wit/deps/sockets/tcp.wit
@@ -2,6 +2,7 @@
 interface tcp {
     use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream};
     use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
+    use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05.{duration};
     use network.{network, error-code, ip-socket-address, ip-address-family};
 
     enum shutdown-type {
@@ -125,8 +126,11 @@ interface tcp {
         /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket:
         /// - `address-family`
         /// - `ipv6-only`
-        /// - `keep-alive`
-        /// - `unicast-hop-limit`
+        /// - `keep-alive-enabled`
+        /// - `keep-alive-idle-time`
+        /// - `keep-alive-interval`
+        /// - `keep-alive-count`
+        /// - `hop-limit`
         /// - `receive-buffer-size`
         /// - `send-buffer-size`
         ///
@@ -176,6 +180,11 @@ interface tcp {
         /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
         remote-address: func() -> result<ip-socket-address, error-code>;
 
+        /// Whether the socket is listening for new connections.
+        ///
+        /// Equivalent to the SO_ACCEPTCONN socket option.
+        is-listening: func() -> bool;
+
         /// Whether this is a IPv4 or IPv6 socket.
         ///
         /// Equivalent to the SO_DOMAIN socket option.
@@ -194,36 +203,87 @@ interface tcp {
 
         /// Hints the desired listen queue size. Implementations are free to ignore this.
         ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        ///
         /// # Typical errors
         /// - `not-supported`:        (set) The platform does not support changing the backlog size after the initial listen.
+        /// - `invalid-argument`:     (set) The provided value was 0.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         set-listen-backlog-size: func(value: u64) -> result<_, error-code>;
 
+        /// Enables or disables keepalive.
+        ///
+        /// The keepalive behavior can be adjusted using:
+        /// - `keep-alive-idle-time`
+        /// - `keep-alive-interval`
+        /// - `keep-alive-count`
+        /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true.
+        ///
         /// Equivalent to the SO_KEEPALIVE socket option.
-        keep-alive: func() -> result<bool, error-code>;
-        set-keep-alive: func(value: bool) -> result<_, error-code>;
+        keep-alive-enabled: func() -> result<bool, error-code>;
+        set-keep-alive-enabled: func(value: bool) -> result<_, error-code>;
+
+        /// Amount of time the connection has to be idle before TCP starts sending keepalive packets.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS)
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-idle-time: func() -> result<duration, error-code>;
+        set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>;
+
+        /// The time between keepalive packets.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPINTVL socket option.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-interval: func() -> result<duration, error-code>;
+        set-keep-alive-interval: func(value: duration) -> result<_, error-code>;
+
+        /// The maximum amount of keepalive packets TCP should send before aborting the connection.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
+        ///
+        /// Equivalent to the TCP_KEEPCNT socket option.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
+        keep-alive-count: func() -> result<u32, error-code>;
+        set-keep-alive-count: func(value: u32) -> result<_, error-code>;
 
         /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
         ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        ///
         /// # Typical errors
         /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         /// - `invalid-state`:        (set) The socket is already in the Listener state.
-        unicast-hop-limit: func() -> result<u8, error-code>;
-        set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
+        hop-limit: func() -> result<u8, error-code>;
+        set-hop-limit: func(value: u8) -> result<_, error-code>;
 
         /// The kernel buffer space reserved for sends/receives on this socket.
         ///
-        /// Note #1: an implementation may choose to cap or round the buffer size when setting the value.
-        ///     In other words, after setting a value, reading the same setting back may return a different value.
-        ///
-        /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of
-        ///     actual data to be sent/received by the application, because the kernel might also use the buffer space
-        ///     for internal metadata structures.
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
         ///
         /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
         ///
         /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
         /// - `invalid-state`:        (set) The socket is already in the Connection state.
         /// - `invalid-state`:        (set) The socket is already in the Listener state.
         receive-buffer-size: func() -> result<u64, error-code>;
diff --git a/crates/wasi/wit/deps/sockets/udp.wit b/crates/wasi/wit/deps/sockets/udp.wit
index 1ee1843a9..9dc1e990b 100644
--- a/crates/wasi/wit/deps/sockets/udp.wit
+++ b/crates/wasi/wit/deps/sockets/udp.wit
@@ -154,19 +154,24 @@ interface udp {
         set-ipv6-only: func(value: bool) -> result<_, error-code>;
 
         /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
+        ///
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
         unicast-hop-limit: func() -> result<u8, error-code>;
         set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
 
         /// The kernel buffer space reserved for sends/receives on this socket.
         ///
-        /// Note #1: an implementation may choose to cap or round the buffer size when setting the value.
-        /// 	In other words, after setting a value, reading the same setting back may return a different value.
-        ///
-        /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of
-        /// 	actual data to be sent/received by the application, because the kernel might also use the buffer space
-        /// 	for internal metadata structures.
+        /// If the provided value is 0, an `invalid-argument` error is returned.
+        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.
+        /// I.e. after setting a value, reading the same setting back may return a different value.
         ///
         /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
+        ///
+        /// # Typical errors
+        /// - `invalid-argument`:     (set) The provided value was 0.
         receive-buffer-size: func() -> result<u64, error-code>;
         set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
         send-buffer-size: func() -> result<u64, error-code>;
diff --git a/crates/wasi/wit/main.wit b/crates/wasi/wit/main.wit
deleted file mode 100644
index 90aa28ad3..000000000
--- a/crates/wasi/wit/main.wit
+++ /dev/null
@@ -1,33 +0,0 @@
-package wasmtime:wasi;
-
-// All of the same imports available in the wasi:cli/command world, but no
-// export required:
-world preview1-adapter-reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-05;
-  import wasi:sockets/network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-05;
-  import wasi:random/random@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-05;
-  import wasi:io/poll@0.2.0-rc-2023-11-05;
-  import wasi:io/streams@0.2.0-rc-2023-11-05;
-  import wasi:cli/environment@0.2.0-rc-2023-11-05;
-  import wasi:cli/exit@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-input@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-output@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stderr@0.2.0-rc-2023-11-05;
-}
diff --git a/winch/codegen/src/abi/mod.rs b/winch/codegen/src/abi/mod.rs
index b4edd3a69..aafaf5ce6 100644
--- a/winch/codegen/src/abi/mod.rs
+++ b/winch/codegen/src/abi/mod.rs
@@ -95,6 +95,15 @@ pub(crate) trait ABI {
     /// Returns the designated floating point scratch register.
     fn float_scratch_reg() -> Reg;
 
+    /// Returns the designated scratch register for the given [WasmType].
+    fn scratch_for(ty: &WasmType) -> Reg {
+        match ty {
+            WasmType::I32 | WasmType::I64 => Self::scratch_reg(),
+            WasmType::F32 | WasmType::F64 => Self::float_scratch_reg(),
+            _ => unimplemented!(),
+        }
+    }
+
     /// Returns the frame pointer register.
     fn fp_reg() -> Reg;
 
diff --git a/winch/codegen/src/codegen/call.rs b/winch/codegen/src/codegen/call.rs
index 977b4757e..981efc139 100644
--- a/winch/codegen/src/codegen/call.rs
+++ b/winch/codegen/src/codegen/call.rs
@@ -99,8 +99,7 @@ impl FnCall {
         let call_stack_space = Self::save(context, masm, &sig);
 
         let reserved_stack = masm.call(arg_stack_space, |masm| {
-            let scratch = <M::ABI as ABI>::scratch_reg();
-            Self::assign(sig, context, masm, scratch);
+            Self::assign(sig, context, masm);
             kind
         });
 
@@ -247,15 +246,11 @@ impl FnCall {
     }
 
     /// Assign arguments for the function call.
-    fn assign<M: MacroAssembler>(
-        sig: &ABISig,
-        context: &mut CodeGenContext,
-        masm: &mut M,
-        scratch: Reg,
-    ) {
+    fn assign<M: MacroAssembler>(sig: &ABISig, context: &mut CodeGenContext, masm: &mut M) {
         let arg_count = sig.params.len();
         let stack = &context.stack;
         let mut stack_values = stack.peekn(arg_count);
+
         for arg in &sig.params {
             let val = stack_values
                 .next()
@@ -267,6 +262,7 @@ impl FnCall {
                 &ABIArg::Stack { ty, offset } => {
                     let addr = masm.address_at_sp(*offset);
                     let size: OperandSize = (*ty).into();
+                    let scratch = <M::ABI as ABI>::scratch_for(&ty);
                     context.move_val_to_reg(val, scratch, masm);
                     masm.store(scratch.into(), addr, size);
                 }
diff --git a/winch/codegen/src/codegen/context.rs b/winch/codegen/src/codegen/context.rs
index ce7551a69..1b7460277 100644
--- a/winch/codegen/src/codegen/context.rs
+++ b/winch/codegen/src/codegen/context.rs
@@ -1,6 +1,6 @@
 use wasmtime_environ::{VMOffsets, WasmHeapType, WasmType};
 
-use super::ControlStackFrame;
+use super::{CodeGen, ControlStackFrame};
 use crate::{
     abi::{ABIResult, ABI},
     codegen::BuiltinFunctions,
@@ -351,10 +351,11 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
         Self::spill_impl(&mut self.stack, &mut self.regalloc, &mut self.frame, masm);
     }
 
-    /// Prepares the compiler to emit an uncoditional jump to the
-    /// given destination branch.  This process involves:
-    /// * Balancing the machine stack pointer by popping it to
-    ///   match the destination branch.
+    /// Prepares the compiler to emit an uncoditional jump to the given
+    /// destination branch.  This process involves:
+    /// * Balancing the machine
+    ///   stack pointer and value stack by popping it to match the destination
+    ///   branch.
     /// * Updating the reachability state.
     /// * Marking the destination frame as a destination target.
     pub fn unconditional_jump<M, F>(&mut self, dest: &mut ControlStackFrame, masm: &mut M, mut f: F)
@@ -362,41 +363,38 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
         M: MacroAssembler,
         F: FnMut(&mut M, &mut Self, &mut ControlStackFrame),
     {
-        let (_, target_sp) = dest.original_stack_len_and_sp_offset();
+        let (target_value_stack, target_sp) = dest.original_stack_len_and_sp_offset();
         // Invariant: The SP, must be greater or equal to the target
         // SP, given that we haven't popped any results by this point
         // yet. But it may happen in the callback.
         assert!(masm.sp_offset() >= target_sp);
         f(masm, self, dest);
 
-        // The following snippet, pops the stack pointer to ensure
-        // that it is correctly placed according to the expectations
-        // of the destination branch.
+        // The following snippet, pops the stack pointer and value stack to
+        // ensure that it is correctly placed according to the expectations of
+        // the destination branch.
         //
-        // This is done in the context of unconditional jumps, as the
-        // machine stack might be left unbalanced at the jump site,
-        // due to register spills. In this context unbalanced refers
-        // to possible extra space created at the jump site, which
-        // might cause invalid memory accesses. Note that in some cases
-        // the stack pointer offset might be already less than or
-        // equal to the original stack pointer offset registered when
-        // entering the destination control stack frame, which
-        // effectively means that when reaching the jump site no extra
-        // space was allocated similar to what would happen in a fall
-        // through in which we assume that the program has allocated
-        // and deallocated the right amount of stack space.
+        // This is done in the context of unconditional jumps, as the machine
+        // stack and value stack might be left unbalanced at the jump site, due
+        // to register spills or extra values in the value stack. Note that in
+        // some cases the stack pointer offset might be already less than or
+        // equal to the original stack pointer offset registered when entering
+        // the destination control stack frame, which effectively means that
+        // when reaching the jump site no extra space was allocated similar to
+        // what would happen in a fall through in which we assume that the
+        // program has allocated and deallocated the right amount of stack
+        // space.
         //
-        // More generally speaking the current stack pointer will be
-        // less than the original stack pointer offset in cases in
-        // which the top value in the value stack is a memory entry
-        // which needs to be popped into the return location according
-        // to the ABI (a register for single value returns and a
-        // memory slot for 1+ returns). This could happen in the
+        // More generally speaking the current stack pointer will be less than
+        // the original stack pointer offset in cases in which the top value in
+        // the value stack is a memory entry which needs to be popped into the
+        // return location according to the ABI (a register for single value
+        // returns and a memory slot for 1+ returns). This could happen in the
         // callback invocation above if the callback invokes
         // `CodeGenContext::pop_abi_results` (e.g. `br` instruction).
         let current_sp = masm.sp_offset();
         if current_sp > target_sp {
-            masm.free_stack(current_sp - target_sp);
+            CodeGen::reset_stack(self, masm, target_value_stack, target_sp);
         }
 
         dest.set_as_target();
@@ -454,11 +452,7 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
             Val::Local(local) => {
                 let slot = frame.get_local(local.index).expect("valid local at slot");
                 let addr = masm.local_address(&slot);
-                let scratch = match slot.ty {
-                    WasmType::I32 | WasmType::I64 => <M::ABI as ABI>::scratch_reg(),
-                    WasmType::F32 | WasmType::F64 => <M::ABI as ABI>::float_scratch_reg(),
-                    WasmType::V128 | WasmType::Ref(_) => unimplemented!(),
-                };
+                let scratch = <M::ABI as ABI>::scratch_for(&slot.ty);
                 masm.load(addr, scratch, slot.ty.into());
                 let stack_slot = masm.push(scratch, slot.ty.into());
                 *v = Val::mem(slot.ty, stack_slot);
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index bb1a8fead..b9120eb34 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -345,12 +345,15 @@ where
     }
 
     /// Pops the value at the stack top and assigns it to the local at
-    ///
     /// the given index, returning the typed register holding the
     /// source value.
-    pub fn emit_set_local(&mut self, addr: M::Address, size: OperandSize) -> TypedReg {
+    pub fn emit_set_local(&mut self, index: u32) -> TypedReg {
         let src = self.context.pop_to_reg(self.masm, None);
-        self.masm.store(RegImm::reg(src.reg), addr, size);
+        // Need to get address of local after `pop_to_reg` since `pop_to_reg`
+        // will pop the machine stack causing an incorrect address to be
+        // calculated.
+        let (ty, addr) = self.context.frame.get_local_address(index, self.masm);
+        self.masm.store(RegImm::reg(src.reg), addr, ty.into());
 
         src
     }
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index d46104f2c..a69992ac1 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -914,8 +914,7 @@ where
 
     // TODO: verify the case where the target local is on the stack.
     fn visit_local_set(&mut self, index: u32) {
-        let (ty, slot) = self.context.frame.get_local_address(index, self.masm);
-        let src = self.emit_set_local(slot, ty.into());
+        let src = self.emit_set_local(index);
         self.context.free_reg(src);
     }
 
@@ -1247,8 +1246,7 @@ where
     }
 
     fn visit_local_tee(&mut self, index: u32) {
-        let (ty, slot) = self.context.frame.get_local_address(index, self.masm);
-        let typed_reg = self.emit_set_local(slot, ty.into());
+        let typed_reg = self.emit_set_local(index);
         self.context.stack.push(typed_reg.into());
     }
 


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/associative-and-commutative.clif b/cranelift/filetests/filetests/egraph/associative-and-commutative.clif
new file mode 100644
index 000000000..37bfa8da5
--- /dev/null
+++ b/cranelift/filetests/filetests/egraph/associative-and-commutative.clif
@@ -0,0 +1,81 @@
+test optimize
+set opt_level=speed
+target x86_64
+
+function %iadd_shallow_and_wide(i32, i32, i32, i32) -> i32 {
+block0(v0: i32, v1: i32, v2: i32, v3: i32):
+    v4 = iadd v0, v1
+    v5 = iadd v4, v2
+    v6 = iadd v5, v3
+    return v6
+; check:  v4 = iadd v0, v1
+; nextln: v7 = iadd v2, v3
+; nextln: v8 = iadd v4, v7
+; check: return v8
+}
+
+function %iadd_shallow_and_wide_twice(i32, i32, i32, i32, i32, i32) -> i32 {
+block0(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32):
+    v6 = iadd v0, v1
+    v7 = iadd v6, v2
+    v8 = iadd v7, v3
+    v9 = iadd v8, v4
+    v10 = iadd v9, v5
+    return v10
+; check:  v6 = iadd v0, v1
+; nextln: v11 = iadd v2, v3
+; nextln: v12 = iadd v6, v11
+; nextln: v16 = iadd v4, v5
+; nextln: v23 = iadd v12, v16
+; nextln: return v23
+}
+
+function %iadd_reassociate_then_cprop(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+    v2 = iconst.i32 42
+    v3 = iconst.i32 36
+    v4 = iadd v0, v2
+    v5 = iadd v1, v3
+    v6 = iadd v4, v5
+    return v6
+; check:  v7 = iadd v0, v1
+; nextln: iconst.i32 78
+; nextln: v16 = iadd v7, v14  ; v14 = 78
+; check:  return v16
+}
+
+function %imul_shallow_and_wide(i32, i32, i32, i32) -> i32 {
+block0(v0: i32, v1: i32, v2: i32, v3: i32):
+    v4 = imul v0, v1
+    v5 = imul v4, v2
+    v6 = imul v5, v3
+    return v6
+; check:  v4 = imul v0, v1
+; nextln: v7 = imul v2, v3
+; nextln: v8 = imul v4, v7
+; check: return v8
+}
+
+function %band_shallow_and_wide(i32, i32, i32, i32) -> i32 {
+block0(v0: i32, v1: i32, v2: i32, v3: i32):
+    v4 = band v0, v1
+    v5 = band v4, v2
+    v6 = band v5, v3
+    return v6
+; check:  v4 = band v0, v1
+; nextln: v7 = band v2, v3
+; nextln: v8 = band v4, v7
+; check: return v8
+}
+
+function %bxor_shallow_and_wide(i32, i32, i32, i32) -> i32 {
+block0(v0: i32, v1: i32, v2: i32, v3: i32):
+    v4 = bxor v0, v1
+    v5 = bxor v4, v2
+    v6 = bxor v5, v3
+    return v6
+; check:  v4 = bxor v0, v1
+; nextln: v7 = bxor v2, v3
+; nextln: v8 = bxor v4, v7
+; check: return v8
+}
diff --git a/cranelift/filetests/filetests/egraph/icmp.clif b/cranelift/filetests/filetests/egraph/icmp.clif
index 1a8874845..ef60cbf95 100644
--- a/cranelift/filetests/filetests/egraph/icmp.clif
+++ b/cranelift/filetests/filetests/egraph/icmp.clif
@@ -53,4 +53,3 @@ block0:
 ;     v5 = icmp ne v2, v3  ; v3 = 0
 ;     return v5
 ; }
-
diff --git a/crates/test-programs/src/bin/preview2_tcp_sockopts.rs b/crates/test-programs/src/bin/preview2_tcp_sockopts.rs
index c54fbccfe..8bc1ef49b 100644
--- a/crates/test-programs/src/bin/preview2_tcp_sockopts.rs
+++ b/crates/test-programs/src/bin/preview2_tcp_sockopts.rs
@@ -3,6 +3,8 @@ use test_programs::wasi::sockets::network::{
 };
 use test_programs::wasi::sockets::tcp::TcpSocket;
 
+const SECOND: u64 = 1_000_000_000;
+
 fn test_tcp_sockopt_defaults(family: IpAddressFamily) {
     let sock = TcpSocket::new(family).unwrap();
 
@@ -12,8 +14,11 @@ fn test_tcp_sockopt_defaults(family: IpAddressFamily) {
         sock.ipv6_only().unwrap(); // Only verify that it has a default value at all, but either value is valid.
     }
 
-    sock.keep_alive().unwrap(); // Only verify that it has a default value at all, but either value is valid.
-    assert!(sock.unicast_hop_limit().unwrap() > 0);
+    sock.keep_alive_enabled().unwrap(); // Only verify that it has a default value at all, but either value is valid.
+    assert!(sock.keep_alive_idle_time().unwrap() > 0);
+    assert!(sock.keep_alive_interval().unwrap() > 0);
+    assert!(sock.keep_alive_count().unwrap() > 0);
+    assert!(sock.hop_limit().unwrap() > 0);
     assert!(sock.receive_buffer_size().unwrap() > 0);
     assert!(sock.send_buffer_size().unwrap() > 0);
 }
@@ -26,22 +31,59 @@ fn test_tcp_sockopt_input_ranges(family: IpAddressFamily) {
         assert!(matches!(sock.set_ipv6_only(false), Ok(_)));
     }
 
-    assert!(matches!(sock.set_listen_backlog_size(0), Ok(_))); // Unsupported sizes should be silently capped.
+    assert!(matches!(
+        sock.set_listen_backlog_size(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_listen_backlog_size(1), Ok(_))); // Unsupported sizes should be silently capped.
     assert!(matches!(sock.set_listen_backlog_size(u64::MAX), Ok(_))); // Unsupported sizes should be silently capped.
 
-    assert!(matches!(sock.set_keep_alive(true), Ok(_)));
-    assert!(matches!(sock.set_keep_alive(false), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(true), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(false), Ok(_)));
 
     assert!(matches!(
-        sock.set_unicast_hop_limit(0),
+        sock.set_keep_alive_idle_time(0),
         Err(ErrorCode::InvalidArgument)
     ));
-    assert!(matches!(sock.set_unicast_hop_limit(1), Ok(_)));
-    assert!(matches!(sock.set_unicast_hop_limit(u8::MAX), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_idle_time(1), Ok(_))); // Unsupported sizes should be silently clamped.
+    let idle_time = sock.keep_alive_idle_time().unwrap(); // Check that the special 0/reset behavior was not triggered by the previous line.
+    assert!(idle_time > 0 && idle_time <= 1 * SECOND);
+    assert!(matches!(sock.set_keep_alive_idle_time(u64::MAX), Ok(_))); // Unsupported sizes should be silently clamped.
 
-    assert!(matches!(sock.set_receive_buffer_size(0), Ok(_))); // Unsupported sizes should be silently capped.
+    assert!(matches!(
+        sock.set_keep_alive_interval(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_keep_alive_interval(1), Ok(_))); // Unsupported sizes should be silently clamped.
+    let idle_time = sock.keep_alive_interval().unwrap(); // Check that the special 0/reset behavior was not triggered by the previous line.
+    assert!(idle_time > 0 && idle_time <= 1 * SECOND);
+    assert!(matches!(sock.set_keep_alive_interval(u64::MAX), Ok(_))); // Unsupported sizes should be silently clamped.
+
+    assert!(matches!(
+        sock.set_keep_alive_count(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_keep_alive_count(1), Ok(_))); // Unsupported sizes should be silently clamped.
+    assert!(matches!(sock.set_keep_alive_count(u32::MAX), Ok(_))); // Unsupported sizes should be silently clamped.
+
+    assert!(matches!(
+        sock.set_hop_limit(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_hop_limit(1), Ok(_)));
+    assert!(matches!(sock.set_hop_limit(u8::MAX), Ok(_)));
+
+    assert!(matches!(
+        sock.set_receive_buffer_size(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_receive_buffer_size(1), Ok(_))); // Unsupported sizes should be silently capped.
     assert!(matches!(sock.set_receive_buffer_size(u64::MAX), Ok(_))); // Unsupported sizes should be silently capped.
-    assert!(matches!(sock.set_send_buffer_size(0), Ok(_))); // Unsupported sizes should be silently capped.
+    assert!(matches!(
+        sock.set_send_buffer_size(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_send_buffer_size(1), Ok(_))); // Unsupported sizes should be silently capped.
     assert!(matches!(sock.set_send_buffer_size(u64::MAX), Ok(_))); // Unsupported sizes should be silently capped.
 }
 
@@ -55,13 +97,22 @@ fn test_tcp_sockopt_readback(family: IpAddressFamily) {
         assert_eq!(sock.ipv6_only().unwrap(), false);
     }
 
-    sock.set_keep_alive(true).unwrap();
-    assert_eq!(sock.keep_alive().unwrap(), true);
-    sock.set_keep_alive(false).unwrap();
-    assert_eq!(sock.keep_alive().unwrap(), false);
+    sock.set_keep_alive_enabled(true).unwrap();
+    assert_eq!(sock.keep_alive_enabled().unwrap(), true);
+    sock.set_keep_alive_enabled(false).unwrap();
+    assert_eq!(sock.keep_alive_enabled().unwrap(), false);
+
+    sock.set_keep_alive_idle_time(42 * SECOND).unwrap();
+    assert_eq!(sock.keep_alive_idle_time().unwrap(), 42 * SECOND);
+
+    sock.set_keep_alive_interval(42 * SECOND).unwrap();
+    assert_eq!(sock.keep_alive_interval().unwrap(), 42 * SECOND);
+
+    sock.set_keep_alive_count(42).unwrap();
+    assert_eq!(sock.keep_alive_count().unwrap(), 42);
 
-    sock.set_unicast_hop_limit(42).unwrap();
-    assert_eq!(sock.unicast_hop_limit().unwrap(), 42);
+    sock.set_hop_limit(42).unwrap();
+    assert_eq!(sock.hop_limit().unwrap(), 42);
 
     sock.set_receive_buffer_size(0x10000).unwrap();
     assert_eq!(sock.receive_buffer_size().unwrap(), 0x10000);
@@ -75,7 +126,7 @@ fn test_tcp_sockopt_inheritance(net: &Network, family: IpAddressFamily) {
     let listener = TcpSocket::new(family).unwrap();
 
     let default_ipv6_only = listener.ipv6_only().unwrap_or(false);
-    let default_keep_alive = listener.keep_alive().unwrap();
+    let default_keep_alive = listener.keep_alive_enabled().unwrap();
 
     // Configure options on listener:
     {
@@ -83,8 +134,13 @@ fn test_tcp_sockopt_inheritance(net: &Network, family: IpAddressFamily) {
             listener.set_ipv6_only(!default_ipv6_only).unwrap();
         }
 
-        listener.set_keep_alive(!default_keep_alive).unwrap();
-        listener.set_unicast_hop_limit(42).unwrap();
+        listener
+            .set_keep_alive_enabled(!default_keep_alive)
+            .unwrap();
+        listener.set_keep_alive_idle_time(42 * SECOND).unwrap();
+        listener.set_keep_alive_interval(42 * SECOND).unwrap();
+        listener.set_keep_alive_count(42).unwrap();
+        listener.set_hop_limit(42).unwrap();
         listener.set_receive_buffer_size(0x10000).unwrap();
         listener.set_send_buffer_size(0x10000).unwrap();
     }
@@ -102,24 +158,39 @@ fn test_tcp_sockopt_inheritance(net: &Network, family: IpAddressFamily) {
             assert_eq!(accepted_client.ipv6_only().unwrap(), !default_ipv6_only);
         }
 
-        assert_eq!(accepted_client.keep_alive().unwrap(), !default_keep_alive);
-        assert_eq!(accepted_client.unicast_hop_limit().unwrap(), 42);
+        assert_eq!(
+            accepted_client.keep_alive_enabled().unwrap(),
+            !default_keep_alive
+        );
+        assert_eq!(accepted_client.keep_alive_idle_time().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_interval().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_count().unwrap(), 42);
+        assert_eq!(accepted_client.hop_limit().unwrap(), 42);
         assert_eq!(accepted_client.receive_buffer_size().unwrap(), 0x10000);
         assert_eq!(accepted_client.send_buffer_size().unwrap(), 0x10000);
     }
 
     // Update options on listener to something else:
     {
-        listener.set_keep_alive(default_keep_alive).unwrap();
-        listener.set_unicast_hop_limit(43).unwrap();
+        listener.set_keep_alive_enabled(default_keep_alive).unwrap();
+        listener.set_keep_alive_idle_time(43 * SECOND).unwrap();
+        listener.set_keep_alive_interval(43 * SECOND).unwrap();
+        listener.set_keep_alive_count(43).unwrap();
+        listener.set_hop_limit(43).unwrap();
         listener.set_receive_buffer_size(0x20000).unwrap();
         listener.set_send_buffer_size(0x20000).unwrap();
     }
 
     // Verify that the already accepted socket was not affected:
     {
-        assert_eq!(accepted_client.keep_alive().unwrap(), !default_keep_alive);
-        assert_eq!(accepted_client.unicast_hop_limit().unwrap(), 42);
+        assert_eq!(
+            accepted_client.keep_alive_enabled().unwrap(),
+            !default_keep_alive
+        );
+        assert_eq!(accepted_client.keep_alive_idle_time().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_interval().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_count().unwrap(), 42);
+        assert_eq!(accepted_client.hop_limit().unwrap(), 42);
         assert_eq!(accepted_client.receive_buffer_size().unwrap(), 0x10000);
         assert_eq!(accepted_client.send_buffer_size().unwrap(), 0x10000);
     }
@@ -132,12 +203,17 @@ fn test_tcp_sockopt_after_listen(net: &Network, family: IpAddressFamily) {
     listener.blocking_listen().unwrap();
     let bound_addr = listener.local_address().unwrap();
 
-    let default_keep_alive = listener.keep_alive().unwrap();
+    let default_keep_alive = listener.keep_alive_enabled().unwrap();
 
     // Update options while the socket is already listening:
     {
-        listener.set_keep_alive(!default_keep_alive).unwrap();
-        listener.set_unicast_hop_limit(42).unwrap();
+        listener
+            .set_keep_alive_enabled(!default_keep_alive)
+            .unwrap();
+        listener.set_keep_alive_idle_time(42 * SECOND).unwrap();
+        listener.set_keep_alive_interval(42 * SECOND).unwrap();
+        listener.set_keep_alive_count(42).unwrap();
+        listener.set_hop_limit(42).unwrap();
         listener.set_receive_buffer_size(0x10000).unwrap();
         listener.set_send_buffer_size(0x10000).unwrap();
     }
@@ -148,8 +224,14 @@ fn test_tcp_sockopt_after_listen(net: &Network, family: IpAddressFamily) {
 
     // Verify options on accepted socket:
     {
-        assert_eq!(accepted_client.keep_alive().unwrap(), !default_keep_alive);
-        assert_eq!(accepted_client.unicast_hop_limit().unwrap(), 42);
+        assert_eq!(
+            accepted_client.keep_alive_enabled().unwrap(),
+            !default_keep_alive
+        );
+        assert_eq!(accepted_client.keep_alive_idle_time().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_interval().unwrap(), 42 * SECOND);
+        assert_eq!(accepted_client.keep_alive_count().unwrap(), 42);
+        assert_eq!(accepted_client.hop_limit().unwrap(), 42);
         assert_eq!(accepted_client.receive_buffer_size().unwrap(), 0x10000);
         assert_eq!(accepted_client.send_buffer_size().unwrap(), 0x10000);
     }
diff --git a/crates/test-programs/src/bin/preview2_tcp_states.rs b/crates/test-programs/src/bin/preview2_tcp_states.rs
index e4439af81..308c6cfd6 100644
--- a/crates/test-programs/src/bin/preview2_tcp_states.rs
+++ b/crates/test-programs/src/bin/preview2_tcp_states.rs
@@ -32,6 +32,7 @@ fn test_tcp_unbound_state_invariants(family: IpAddressFamily) {
         sock.remote_address(),
         Err(ErrorCode::InvalidState)
     ));
+    assert_eq!(sock.is_listening(), false);
     assert_eq!(sock.address_family(), family);
 
     if family == IpAddressFamily::Ipv6 {
@@ -49,10 +50,16 @@ fn test_tcp_unbound_state_invariants(family: IpAddressFamily) {
     }
 
     assert!(matches!(sock.set_listen_backlog_size(32), Ok(_)));
-    assert!(matches!(sock.keep_alive(), Ok(_)));
-    assert!(matches!(sock.set_keep_alive(false), Ok(_)));
-    assert!(matches!(sock.unicast_hop_limit(), Ok(_)));
-    assert!(matches!(sock.set_unicast_hop_limit(255), Ok(_)));
+    assert!(matches!(sock.keep_alive_enabled(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(false), Ok(_)));
+    assert!(matches!(sock.keep_alive_idle_time(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_idle_time(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_interval(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_interval(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_count(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_count(1), Ok(_)));
+    assert!(matches!(sock.hop_limit(), Ok(_)));
+    assert!(matches!(sock.set_hop_limit(255), Ok(_)));
     assert!(matches!(sock.receive_buffer_size(), Ok(_)));
     assert!(matches!(sock.set_receive_buffer_size(16000), Ok(_)));
     assert!(matches!(sock.send_buffer_size(), Ok(_)));
@@ -90,6 +97,7 @@ fn test_tcp_bound_state_invariants(net: &Network, family: IpAddressFamily) {
         sock.remote_address(),
         Err(ErrorCode::InvalidState)
     ));
+    assert_eq!(sock.is_listening(), false);
     assert_eq!(sock.address_family(), family);
 
     if family == IpAddressFamily::Ipv6 {
@@ -107,10 +115,16 @@ fn test_tcp_bound_state_invariants(net: &Network, family: IpAddressFamily) {
     }
 
     assert!(matches!(sock.set_listen_backlog_size(32), Ok(_)));
-    assert!(matches!(sock.keep_alive(), Ok(_)));
-    assert!(matches!(sock.set_keep_alive(false), Ok(_)));
-    assert!(matches!(sock.unicast_hop_limit(), Ok(_)));
-    assert!(matches!(sock.set_unicast_hop_limit(255), Ok(_)));
+    assert!(matches!(sock.keep_alive_enabled(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(false), Ok(_)));
+    assert!(matches!(sock.keep_alive_idle_time(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_idle_time(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_interval(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_interval(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_count(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_count(1), Ok(_)));
+    assert!(matches!(sock.hop_limit(), Ok(_)));
+    assert!(matches!(sock.set_hop_limit(255), Ok(_)));
     assert!(matches!(sock.receive_buffer_size(), Ok(_)));
     assert!(matches!(sock.set_receive_buffer_size(16000), Ok(_)));
     assert!(matches!(sock.send_buffer_size(), Ok(_)));
@@ -152,6 +166,7 @@ fn test_tcp_listening_state_invariants(net: &Network, family: IpAddressFamily) {
         sock.remote_address(),
         Err(ErrorCode::InvalidState)
     ));
+    assert_eq!(sock.is_listening(), true);
     assert_eq!(sock.address_family(), family);
 
     if family == IpAddressFamily::Ipv6 {
@@ -172,10 +187,16 @@ fn test_tcp_listening_state_invariants(net: &Network, family: IpAddressFamily) {
         sock.set_listen_backlog_size(32),
         Ok(_) | Err(ErrorCode::NotSupported)
     ));
-    assert!(matches!(sock.keep_alive(), Ok(_)));
-    assert!(matches!(sock.set_keep_alive(false), Ok(_)));
-    assert!(matches!(sock.unicast_hop_limit(), Ok(_)));
-    assert!(matches!(sock.set_unicast_hop_limit(255), Ok(_)));
+    assert!(matches!(sock.keep_alive_enabled(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(false), Ok(_)));
+    assert!(matches!(sock.keep_alive_idle_time(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_idle_time(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_interval(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_interval(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_count(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_count(1), Ok(_)));
+    assert!(matches!(sock.hop_limit(), Ok(_)));
+    assert!(matches!(sock.set_hop_limit(255), Ok(_)));
     assert!(matches!(sock.receive_buffer_size(), Ok(_)));
     assert!(matches!(sock.set_receive_buffer_size(16000), Ok(_)));
     assert!(matches!(sock.send_buffer_size(), Ok(_)));
@@ -214,6 +235,7 @@ fn test_tcp_connected_state_invariants(net: &Network, family: IpAddressFamily) {
 
     assert!(matches!(sock.local_address(), Ok(_)));
     assert!(matches!(sock.remote_address(), Ok(_)));
+    assert_eq!(sock.is_listening(), false);
     assert_eq!(sock.address_family(), family);
 
     if family == IpAddressFamily::Ipv6 {
@@ -230,10 +252,16 @@ fn test_tcp_connected_state_invariants(net: &Network, family: IpAddressFamily) {
         ));
     }
 
-    assert!(matches!(sock.keep_alive(), Ok(_)));
-    assert!(matches!(sock.set_keep_alive(false), Ok(_)));
-    assert!(matches!(sock.unicast_hop_limit(), Ok(_)));
-    assert!(matches!(sock.set_unicast_hop_limit(255), Ok(_)));
+    assert!(matches!(sock.keep_alive_enabled(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_enabled(false), Ok(_)));
+    assert!(matches!(sock.keep_alive_idle_time(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_idle_time(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_interval(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_interval(1), Ok(_)));
+    assert!(matches!(sock.keep_alive_count(), Ok(_)));
+    assert!(matches!(sock.set_keep_alive_count(1), Ok(_)));
+    assert!(matches!(sock.hop_limit(), Ok(_)));
+    assert!(matches!(sock.set_hop_limit(255), Ok(_)));
     assert!(matches!(sock.receive_buffer_size(), Ok(_)));
     assert!(matches!(sock.set_receive_buffer_size(16000), Ok(_)));
     assert!(matches!(sock.send_buffer_size(), Ok(_)));
diff --git a/crates/test-programs/src/bin/preview2_udp_sockopts.rs b/crates/test-programs/src/bin/preview2_udp_sockopts.rs
index b9cebbcb3..f705b476c 100644
--- a/crates/test-programs/src/bin/preview2_udp_sockopts.rs
+++ b/crates/test-programs/src/bin/preview2_udp_sockopts.rs
@@ -30,9 +30,17 @@ fn test_udp_sockopt_input_ranges(family: IpAddressFamily) {
     assert!(matches!(sock.set_unicast_hop_limit(1), Ok(_)));
     assert!(matches!(sock.set_unicast_hop_limit(u8::MAX), Ok(_)));
 
-    assert!(matches!(sock.set_receive_buffer_size(0), Ok(_))); // Unsupported sizes should be silently capped.
+    assert!(matches!(
+        sock.set_receive_buffer_size(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_receive_buffer_size(1), Ok(_))); // Unsupported sizes should be silently capped.
     assert!(matches!(sock.set_receive_buffer_size(u64::MAX), Ok(_))); // Unsupported sizes should be silently capped.
-    assert!(matches!(sock.set_send_buffer_size(0), Ok(_))); // Unsupported sizes should be silently capped.
+    assert!(matches!(
+        sock.set_send_buffer_size(0),
+        Err(ErrorCode::InvalidArgument)
+    ));
+    assert!(matches!(sock.set_send_buffer_size(1), Ok(_))); // Unsupported sizes should be silently capped.
     assert!(matches!(sock.set_send_buffer_size(u64::MAX), Ok(_))); // Unsupported sizes should be silently capped.
 }
 
diff --git a/crates/wasi-http/wit/test.wit b/crates/wasi-http/wit/test.wit
index 5e7557ed6..fc31f6ef3 100644
--- a/crates/wasi-http/wit/test.wit
+++ b/crates/wasi-http/wit/test.wit
@@ -1,3 +1,5 @@
+package wasmtime:wasi;
+
 // only used as part of `test-programs`
 world test-reactor {
   include wasi:cli/reactor@0.2.0-rc-2023-11-05;
diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit
index 5e7557ed6..fc31f6ef3 100644
--- a/crates/wasi/wit/test.wit
+++ b/crates/wasi/wit/test.wit
@@ -1,3 +1,5 @@
+package wasmtime:wasi;
+
 // only used as part of `test-programs`
 world test-reactor {
   include wasi:cli/reactor@0.2.0-rc-2023-11-05;
diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs
deleted file mode 100644
index fdfc22a7f..000000000
--- a/tests/all/custom_signal_handler.rs
+++ /dev/null
@@ -1,336 +0,0 @@
-#[cfg(any(
-    target_os = "linux",
-    all(target_os = "macos", feature = "posix-signals-on-macos")
-))]
-#[cfg(not(miri))]
-mod tests {
-    use anyhow::Result;
-    use rustix::mm::{mprotect, MprotectFlags};
-    use std::sync::atomic::{AtomicBool, Ordering};
-    use std::sync::Arc;
-    use wasmtime::unix::StoreExt;
-    use wasmtime::*;
-
-    const WAT1: &str = r#"
-(module
-  (func $hostcall_read (import "" "hostcall_read") (result i32))
-  (func $read (export "read") (result i32)
-    (i32.load (i32.const 0))
-  )
-  (func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
-    (i32.load
-      (i32.mul
-        ;; memory size in Wasm pages
-        (memory.size)
-        ;; Wasm page size
-        (i32.const 65536)
-      )
-    )
-  )
-  (func (export "hostcall_read") (result i32)
-    call $hostcall_read
-  )
-  (func $start
-    (i32.store (i32.const 0) (i32.const 123))
-  )
-  (start $start)
-  (memory (export "memory") 1 4)
-)
-"#;
-
-    const WAT2: &str = r#"
-(module
-  (import "other_module" "read" (func $other_module.read (result i32)))
-  (func $run (export "run") (result i32)
-      call $other_module.read)
-)
-"#;
-
-    fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
-        let ret = instance
-            .get_typed_func::<(), i32>(&mut *store, func_name)?
-            .call(store, ())?;
-        Ok(ret)
-    }
-
-    // Locate "memory" export, get base address and size and set memory protection to PROT_NONE
-    fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
-        let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
-        let base = mem_export.data_ptr(&store);
-        let length = mem_export.data_size(&store);
-
-        // So we can later trigger SIGSEGV by performing a read
-        unsafe {
-            mprotect(
-                base as *mut std::ffi::c_void,
-                length,
-                MprotectFlags::empty(),
-            )
-            .unwrap();
-        }
-
-        println!("memory: base={:?}, length={}", base, length);
-
-        (base as usize, length)
-    }
-
-    fn handle_sigsegv(
-        base: usize,
-        length: usize,
-        signum: libc::c_int,
-        siginfo: *const libc::siginfo_t,
-    ) -> bool {
-        println!("Hello from instance signal handler!");
-        // SIGSEGV on Linux, SIGBUS on Mac
-        if libc::SIGSEGV == signum || libc::SIGBUS == signum {
-            let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
-            // Any signal from within module's memory we handle ourselves
-            let result = (si_addr as u64) < (base as u64) + (length as u64);
-            // Remove protections so the execution may resume
-            unsafe {
-                mprotect(
-                    base as *mut libc::c_void,
-                    length,
-                    MprotectFlags::READ | MprotectFlags::WRITE,
-                )
-                .unwrap();
-            }
-            println!("signal handled: {}", result);
-            result
-        } else {
-            // Otherwise, we forward to wasmtime's signal handler.
-            false
-        }
-    }
-
-    fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
-        module
-            .imports()
-            .map(|import| {
-                assert_eq!("hostcall_read", import.name());
-                let func = Func::wrap(&mut *store, {
-                    move |mut caller: Caller<'_, _>| {
-                        let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
-                        let memory = mem.data(&caller);
-                        i32::from_le_bytes(memory[0..4].try_into().unwrap())
-                    }
-                });
-                wasmtime::Extern::Func(func)
-            })
-            .collect::<Vec<_>>()
-    }
-
-    // This test will only succeed if the SIGSEGV signal originating from the
-    // hostcall can be handled.
-    #[test]
-    fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        let externs = make_externs(&mut store, &module);
-        let instance = Instance::new(&mut store, &module, &externs)?;
-
-        let (base, length) = set_up_memory(&mut store, instance);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base, length, signum, siginfo)
-            });
-        }
-        println!("calling hostcall_read...");
-        let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
-        assert_eq!(123, result);
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_single_instance() -> Result<()> {
-        let engine = Engine::new(&Config::default())?;
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        let externs = make_externs(&mut store, &module);
-        let instance = Instance::new(&mut store, &module, &externs)?;
-
-        let (base, length) = set_up_memory(&mut store, instance);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base, length, signum, siginfo)
-            });
-        }
-
-        // these invoke wasmtime_call_trampoline from action.rs
-        {
-            println!("calling read...");
-            let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
-            assert_eq!(123, result);
-        }
-
-        {
-            println!("calling read_out_of_bounds...");
-            let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
-                .unwrap_err()
-                .downcast::<Trap>()?;
-            assert_eq!(trap, Trap::MemoryOutOfBounds);
-        }
-
-        // these invoke wasmtime_call_trampoline from callable.rs
-        {
-            let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
-            println!("calling read...");
-            let result = read_func
-                .call(&mut store, ())
-                .expect("expected function not to trap");
-            assert_eq!(123i32, result);
-        }
-
-        {
-            let read_out_of_bounds_func =
-                instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
-            println!("calling read_out_of_bounds...");
-            let trap = read_out_of_bounds_func
-                .call(&mut store, ())
-                .unwrap_err()
-                .downcast::<Trap>()?;
-            assert_eq!(trap, Trap::MemoryOutOfBounds);
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_multiple_instances() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-        let module = Module::new(&engine, WAT1)?;
-
-        // Set up multiple instances
-
-        let externs = make_externs(&mut store, &module);
-        let instance1 = Instance::new(&mut store, &module, &externs)?;
-        let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
-
-        unsafe {
-            let (base1, length1) = set_up_memory(&mut store, instance1);
-
-            store.set_signal_handler({
-                let instance1_handler_triggered = instance1_handler_triggered.clone();
-                move |_signum, _siginfo, _context| {
-                    // Remove protections so the execution may resume
-                    mprotect(
-                        base1 as *mut libc::c_void,
-                        length1,
-                        MprotectFlags::READ | MprotectFlags::WRITE,
-                    )
-                    .unwrap();
-                    instance1_handler_triggered.store(true, Ordering::SeqCst);
-                    println!(
-                        "Hello from instance1 signal handler! {}",
-                        instance1_handler_triggered.load(Ordering::SeqCst)
-                    );
-                    true
-                }
-            });
-        }
-
-        // Invoke both instances and trigger both signal handlers
-
-        // First instance1
-        {
-            let mut exports1 = instance1.exports(&mut store);
-            assert!(exports1.next().is_some());
-            drop(exports1);
-
-            println!("calling instance1.read...");
-            let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
-            assert_eq!(123, result);
-            assert_eq!(
-                instance1_handler_triggered.load(Ordering::SeqCst),
-                true,
-                "instance1 signal handler has been triggered"
-            );
-        }
-
-        let externs = make_externs(&mut store, &module);
-        let instance2 =
-            Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
-        let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
-
-        unsafe {
-            let (base2, length2) = set_up_memory(&mut store, instance2);
-
-            store.set_signal_handler({
-                let instance2_handler_triggered = instance2_handler_triggered.clone();
-                move |_signum, _siginfo, _context| {
-                    // Remove protections so the execution may resume
-                    mprotect(
-                        base2 as *mut libc::c_void,
-                        length2,
-                        MprotectFlags::READ | MprotectFlags::WRITE,
-                    )
-                    .unwrap();
-                    instance2_handler_triggered.store(true, Ordering::SeqCst);
-                    println!(
-                        "Hello from instance2 signal handler! {}",
-                        instance2_handler_triggered.load(Ordering::SeqCst)
-                    );
-                    true
-                }
-            });
-        }
-
-        // And then instance2
-        {
-            let mut exports2 = instance2.exports(&mut store);
-            assert!(exports2.next().is_some());
-            drop(exports2);
-
-            println!("calling instance2.read...");
-            let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
-            assert_eq!(123, result);
-            assert_eq!(
-                instance2_handler_triggered.load(Ordering::SeqCst),
-                true,
-                "instance1 signal handler has been triggered"
-            );
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
-        let engine = Engine::default();
-        let mut store = Store::new(&engine, ());
-
-        // instance1 which defines 'read'
-        let module1 = Module::new(&engine, WAT1)?;
-        let externs = make_externs(&mut store, &module1);
-        let instance1 = Instance::new(&mut store, &module1, &externs)?;
-        let (base1, length1) = set_up_memory(&mut store, instance1);
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                println!("instance1");
-                handle_sigsegv(base1, length1, signum, siginfo)
-            });
-        }
-
-        let mut instance1_exports = instance1.exports(&mut store);
-        let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
-        drop(instance1_exports);
-
-        // instance2 which calls 'instance1.read'
-        let module2 = Module::new(&engine, WAT2)?;
-        let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
-        // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
-        // SIGSEGV originating from within the memory of instance1
-        unsafe {
-            store.set_signal_handler(move |signum, siginfo, _| {
-                handle_sigsegv(base1, length1, signum, siginfo)
-            });
-        }
-
-        println!("calling instance2.run");
-        let result = invoke_export(&mut store, instance2, "run")?;
-        assert_eq!(123, result);
-        Ok(())
-    }
-}
diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs
index 6686daf5c..7f04a541e 100644
--- a/tests/all/fuel.rs
+++ b/tests/all/fuel.rs
@@ -220,3 +220,40 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() {
     let consumed_fuel = init_fuel - store.get_fuel().unwrap();
     assert!(consumed_fuel > 0);
 }
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn get_fuel_clamps_at_zero() -> Result<()> {
+    let engine = Engine::new(Config::new().consume_fuel(true))?;
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+(module
+  (func $add2 (export "add2") (param $n i32) (result i32)
+    (i32.add (local.get $n) (i32.const 2))
+  )
+)
+        "#,
+    )?;
+    let instance = Instance::new(&mut store, &module, &[])?;
+
+    let add2 = instance.get_typed_func::<i32, i32>(&mut store, "add2")?;
+
+    // Start with 6 fuel and one invocation of this function should cost 4 fuel
+    store.set_fuel(6)?;
+    assert_eq!(store.get_fuel()?, 6);
+    add2.call(&mut store, 10)?;
+    assert_eq!(store.get_fuel()?, 2);
+
+    // One more invocation of the function would technically take us to -2 fuel,
+    // but that's not representable, so the store should report 0 fuel after
+    // this completes.
+    add2.call(&mut store, 10)?;
+    assert_eq!(store.get_fuel()?, 0);
+
+    // Any further attempts should fail.
+    assert!(add2.call(&mut store, 10).is_err());
+
+    Ok(())
+}
diff --git a/tests/all/main.rs b/tests/all/main.rs
index 79ad24c60..cffe15555 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -6,7 +6,6 @@ mod cli_tests;
 mod code_too_large;
 mod component_model;
 mod coredump;
-mod custom_signal_handler;
 mod debug;
 mod epoch_interruption;
 mod externals;
diff --git a/tests/custom_signal_handler.rs b/tests/custom_signal_handler.rs
new file mode 100644
index 000000000..07a4a4ee0
--- /dev/null
+++ b/tests/custom_signal_handler.rs
@@ -0,0 +1,341 @@
+// NB: this is a separate test file because on macOS this should use signals
+// instead of Mach Ports and only one can be configured globally per process.
+
+#![cfg(any(target_os = "linux", target_os = "macos"))]
+#![cfg(not(miri))]
+
+use anyhow::Result;
+use rustix::mm::{mprotect, MprotectFlags};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use wasmtime::unix::StoreExt;
+use wasmtime::*;
+
+const WAT1: &str = r#"
+(module
+  (func $hostcall_read (import "" "hostcall_read") (result i32))
+  (func $read (export "read") (result i32)
+    (i32.load (i32.const 0))
+  )
+  (func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
+    (i32.load
+      (i32.mul
+        ;; memory size in Wasm pages
+        (memory.size)
+        ;; Wasm page size
+        (i32.const 65536)
+      )
+    )
+  )
+  (func (export "hostcall_read") (result i32)
+    call $hostcall_read
+  )
+  (func $start
+    (i32.store (i32.const 0) (i32.const 123))
+  )
+  (start $start)
+  (memory (export "memory") 1 4)
+)
+"#;
+
+const WAT2: &str = r#"
+(module
+  (import "other_module" "read" (func $other_module.read (result i32)))
+  (func $run (export "run") (result i32)
+      call $other_module.read)
+)
+"#;
+
+fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
+    let ret = instance
+        .get_typed_func::<(), i32>(&mut *store, func_name)?
+        .call(store, ())?;
+    Ok(ret)
+}
+
+// Locate "memory" export, get base address and size and set memory protection to PROT_NONE
+fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
+    let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
+    let base = mem_export.data_ptr(&store);
+    let length = mem_export.data_size(&store);
+
+    // So we can later trigger SIGSEGV by performing a read
+    unsafe {
+        mprotect(
+            base as *mut std::ffi::c_void,
+            length,
+            MprotectFlags::empty(),
+        )
+        .unwrap();
+    }
+
+    println!("memory: base={:?}, length={}", base, length);
+
+    (base as usize, length)
+}
+
+fn handle_sigsegv(
+    base: usize,
+    length: usize,
+    signum: libc::c_int,
+    siginfo: *const libc::siginfo_t,
+) -> bool {
+    println!("Hello from instance signal handler!");
+    // SIGSEGV on Linux, SIGBUS on Mac
+    if libc::SIGSEGV == signum || libc::SIGBUS == signum {
+        let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
+        // Any signal from within module's memory we handle ourselves
+        let result = (si_addr as u64) < (base as u64) + (length as u64);
+        // Remove protections so the execution may resume
+        unsafe {
+            mprotect(
+                base as *mut libc::c_void,
+                length,
+                MprotectFlags::READ | MprotectFlags::WRITE,
+            )
+            .unwrap();
+        }
+        println!("signal handled: {}", result);
+        result
+    } else {
+        // Otherwise, we forward to wasmtime's signal handler.
+        false
+    }
+}
+
+fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
+    module
+        .imports()
+        .map(|import| {
+            assert_eq!("hostcall_read", import.name());
+            let func = Func::wrap(&mut *store, {
+                move |mut caller: Caller<'_, _>| {
+                    let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
+                    let memory = mem.data(&caller);
+                    i32::from_le_bytes(memory[0..4].try_into().unwrap())
+                }
+            });
+            wasmtime::Extern::Func(func)
+        })
+        .collect::<Vec<_>>()
+}
+
+fn engine() -> Engine {
+    let mut config = Config::new();
+    config.macos_use_mach_ports(false);
+    Engine::new(&config).unwrap()
+}
+
+// This test will only succeed if the SIGSEGV signal originating from the
+// hostcall can be handled.
+#[test]
+fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    let externs = make_externs(&mut store, &module);
+    let instance = Instance::new(&mut store, &module, &externs)?;
+
+    let (base, length) = set_up_memory(&mut store, instance);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base, length, signum, siginfo)
+        });
+    }
+    println!("calling hostcall_read...");
+    let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
+    assert_eq!(123, result);
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_single_instance() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    let externs = make_externs(&mut store, &module);
+    let instance = Instance::new(&mut store, &module, &externs)?;
+
+    let (base, length) = set_up_memory(&mut store, instance);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base, length, signum, siginfo)
+        });
+    }
+
+    // these invoke wasmtime_call_trampoline from action.rs
+    {
+        println!("calling read...");
+        let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
+        assert_eq!(123, result);
+    }
+
+    {
+        println!("calling read_out_of_bounds...");
+        let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
+            .unwrap_err()
+            .downcast::<Trap>()?;
+        assert_eq!(trap, Trap::MemoryOutOfBounds);
+    }
+
+    // these invoke wasmtime_call_trampoline from callable.rs
+    {
+        let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
+        println!("calling read...");
+        let result = read_func
+            .call(&mut store, ())
+            .expect("expected function not to trap");
+        assert_eq!(123i32, result);
+    }
+
+    {
+        let read_out_of_bounds_func =
+            instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
+        println!("calling read_out_of_bounds...");
+        let trap = read_out_of_bounds_func
+            .call(&mut store, ())
+            .unwrap_err()
+            .downcast::<Trap>()?;
+        assert_eq!(trap, Trap::MemoryOutOfBounds);
+    }
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_multiple_instances() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(&engine, WAT1)?;
+
+    // Set up multiple instances
+
+    let externs = make_externs(&mut store, &module);
+    let instance1 = Instance::new(&mut store, &module, &externs)?;
+    let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
+
+    unsafe {
+        let (base1, length1) = set_up_memory(&mut store, instance1);
+
+        store.set_signal_handler({
+            let instance1_handler_triggered = instance1_handler_triggered.clone();
+            move |_signum, _siginfo, _context| {
+                // Remove protections so the execution may resume
+                mprotect(
+                    base1 as *mut libc::c_void,
+                    length1,
+                    MprotectFlags::READ | MprotectFlags::WRITE,
+                )
+                .unwrap();
+                instance1_handler_triggered.store(true, Ordering::SeqCst);
+                println!(
+                    "Hello from instance1 signal handler! {}",
+                    instance1_handler_triggered.load(Ordering::SeqCst)
+                );
+                true
+            }
+        });
+    }
+
+    // Invoke both instances and trigger both signal handlers
+
+    // First instance1
+    {
+        let mut exports1 = instance1.exports(&mut store);
+        assert!(exports1.next().is_some());
+        drop(exports1);
+
+        println!("calling instance1.read...");
+        let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
+        assert_eq!(123, result);
+        assert_eq!(
+            instance1_handler_triggered.load(Ordering::SeqCst),
+            true,
+            "instance1 signal handler has been triggered"
+        );
+    }
+
+    let externs = make_externs(&mut store, &module);
+    let instance2 =
+        Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
+    let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
+
+    unsafe {
+        let (base2, length2) = set_up_memory(&mut store, instance2);
+
+        store.set_signal_handler({
+            let instance2_handler_triggered = instance2_handler_triggered.clone();
+            move |_signum, _siginfo, _context| {
+                // Remove protections so the execution may resume
+                mprotect(
+                    base2 as *mut libc::c_void,
+                    length2,
+                    MprotectFlags::READ | MprotectFlags::WRITE,
+                )
+                .unwrap();
+                instance2_handler_triggered.store(true, Ordering::SeqCst);
+                println!(
+                    "Hello from instance2 signal handler! {}",
+                    instance2_handler_triggered.load(Ordering::SeqCst)
+                );
+                true
+            }
+        });
+    }
+
+    // And then instance2
+    {
+        let mut exports2 = instance2.exports(&mut store);
+        assert!(exports2.next().is_some());
+        drop(exports2);
+
+        println!("calling instance2.read...");
+        let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
+        assert_eq!(123, result);
+        assert_eq!(
+            instance2_handler_triggered.load(Ordering::SeqCst),
+            true,
+            "instance1 signal handler has been triggered"
+        );
+    }
+    Ok(())
+}
+
+#[test]
+fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
+    let engine = engine();
+    let mut store = Store::new(&engine, ());
+
+    // instance1 which defines 'read'
+    let module1 = Module::new(&engine, WAT1)?;
+    let externs = make_externs(&mut store, &module1);
+    let instance1 = Instance::new(&mut store, &module1, &externs)?;
+    let (base1, length1) = set_up_memory(&mut store, instance1);
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            println!("instance1");
+            handle_sigsegv(base1, length1, signum, siginfo)
+        });
+    }
+
+    let mut instance1_exports = instance1.exports(&mut store);
+    let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
+    drop(instance1_exports);
+
+    // instance2 which calls 'instance1.read'
+    let module2 = Module::new(&engine, WAT2)?;
+    let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
+    // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
+    // SIGSEGV originating from within the memory of instance1
+    unsafe {
+        store.set_signal_handler(move |signum, siginfo, _| {
+            handle_sigsegv(base1, length1, signum, siginfo)
+        });
+    }
+
+    println!("calling instance2.run");
+    let result = invoke_export(&mut store, instance2, "run")?;
+    assert_eq!(123, result);
+    Ok(())
+}
diff --git a/tests/misc_testsuite/winch/call.wast b/tests/misc_testsuite/winch/call.wast
index 84e92519a..c76b20387 100644
--- a/tests/misc_testsuite/winch/call.wast
+++ b/tests/misc_testsuite/winch/call.wast
@@ -144,6 +144,13 @@
     (block (result i32) (i32.const 2) (call $const-i32) (br_table 0 0))
   )
 
+  (func $return-from-long-argument-list-helper (param f32 i32 i32 f64 f32 f32 f32 f64 f32 i32 i32 f32 f64 i64 i64 i32 i64 i64 f32 i64 i64 i64 i32 f32 f32 f32 f64 f32 i32 i64 f32 f64 f64 f32 i32 f32 f32 f64 i64 f64 i32 i64 f32 f64 i32 i32 i32 i64 f64 i32 i64 i64 f64 f64 f64 f64 f64 f64 i32 f32 f64 f64 i32 i64 f32 f32 f32 i32 f64 f64 f64 f64 f64 f32 i64 i64 i32 i32 i32 f32 f64 i32 i64 f32 f32 f32 i32 i32 f32 f64 i64 f32 f64 f32 f32 f32 i32 f32 i64 i32) (result i32)
+    (local.get 99)
+  )
+
+  (func (export "return-from-long-argument-list") (param i32) (result i32)
+    (call $return-from-long-argument-list-helper (f32.const 0) (i32.const 0) (i32.const 0) (f64.const 0) (f32.const 0) (f32.const 0) (f32.const 0) (f64.const 0) (f32.const 0) (i32.const 0) (i32.const 0) (f32.const 0) (f64.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i64.const 0) (i64.const 0) (f32.const 0) (i64.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (f32.const 0) (f32.const 0) (f32.const 0) (f64.const 0) (f32.const 0) (i32.const 0) (i64.const 0) (f32.const 0) (f64.const 0) (f64.const 0) (f32.const 0) (i32.const 0) (f32.const 0) (f32.const 0) (f64.const 0) (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (f32.const 0) (f64.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (i64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (i32.const 0) (f32.const 0) (f64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (f32.const 0) (f32.const 0) (f32.const 0) (i32.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f64.const 0) (f32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (f32.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (f32.const 0) (f32.const 0) (f32.const 0) (i32.const 0) (i32.const 0) (f32.const 0) (f64.const 0) (i64.const 0) (f32.const 0) (f64.const 0) (f32.const 0) (f32.const 0) (f32.const 0) (i32.const 0) (f32.const 0) (i64.const 0) (local.get 0))
+  )
 )
 
 (assert_return (invoke "type-i32") (i32.const 0x132))
@@ -193,3 +200,4 @@
 
 (assert_return (invoke "as-br_table-first") (i32.const 0x132))
 (assert_return (invoke "as-br_table-last") (i32.const 2))
+(assert_return (invoke "return-from-long-argument-list" (i32.const 42)) (i32.const 42))
diff --git a/winch/filetests/filetests/x64/block/get_and_set.wat b/winch/filetests/filetests/x64/block/get_and_set.wat
new file mode 100644
index 000000000..3b944e74a
--- /dev/null
+++ b/winch/filetests/filetests/x64/block/get_and_set.wat
@@ -0,0 +1,22 @@
+;;! target = "x86_64"
+
+(module
+  (func (export "") (param i32)
+    local.get 0
+    block
+    end
+    local.set 0
+  )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 897c240c             	mov	dword ptr [rsp + 0xc], edi
+;;    c:	 4c89742404           	mov	qword ptr [rsp + 4], r14
+;;   11:	 448b5c240c           	mov	r11d, dword ptr [rsp + 0xc]
+;;   16:	 4153                 	push	r11
+;;   18:	 58                   	pop	rax
+;;   19:	 8944240c             	mov	dword ptr [rsp + 0xc], eax
+;;   1d:	 4883c410             	add	rsp, 0x10
+;;   21:	 5d                   	pop	rbp
+;;   22:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/block/get_and_tee.wat b/winch/filetests/filetests/x64/block/get_and_tee.wat
new file mode 100644
index 000000000..1fb77d09a
--- /dev/null
+++ b/winch/filetests/filetests/x64/block/get_and_tee.wat
@@ -0,0 +1,22 @@
+;;! target = "x86_64"
+
+(module
+  (func (export "") (param i32) (result i32)
+    local.get 0
+    block
+    end
+    local.tee 0
+  )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 897c240c             	mov	dword ptr [rsp + 0xc], edi
+;;    c:	 4c89742404           	mov	qword ptr [rsp + 4], r14
+;;   11:	 448b5c240c           	mov	r11d, dword ptr [rsp + 0xc]
+;;   16:	 4153                 	push	r11
+;;   18:	 58                   	pop	rax
+;;   19:	 8944240c             	mov	dword ptr [rsp + 0xc], eax
+;;   1d:	 4883c410             	add	rsp, 0x10
+;;   21:	 5d                   	pop	rbp
+;;   22:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/br/br_jump.wat b/winch/filetests/filetests/x64/br/br_jump.wat
new file mode 100644
index 000000000..b32e98f73
--- /dev/null
+++ b/winch/filetests/filetests/x64/br/br_jump.wat
@@ -0,0 +1,30 @@
+;;! target = "x86_64"
+(module
+  (func (;0;) (result i32)
+    (local i32)
+    local.get 0
+    loop ;; label = @1
+      local.get 0
+      block ;; label = @2
+      end
+      br 0 (;@1;)
+    end
+  )
+  (export "" (func 0))
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 48c744240800000000   	
+;; 				mov	qword ptr [rsp + 8], 0
+;;   11:	 4c89742404           	mov	qword ptr [rsp + 4], r14
+;;   16:	 448b5c240c           	mov	r11d, dword ptr [rsp + 0xc]
+;;   1b:	 4153                 	push	r11
+;;   1d:	 448b5c2414           	mov	r11d, dword ptr [rsp + 0x14]
+;;   22:	 4153                 	push	r11
+;;   24:	 4883c408             	add	rsp, 8
+;;   28:	 e9f0ffffff           	jmp	0x1d
+;;   2d:	 4883c408             	add	rsp, 8
+;;   31:	 4883c410             	add	rsp, 0x10
+;;   35:	 5d                   	pop	rbp
+;;   36:	 c3                   	ret