Repository: bytecodealliance/wasmtime
Pull request #7649 (2023-12-06 18:59:46)

Use the compilation cache for components
Cache whole components in the compilation cache. This doesn't address the situation where we could cache individual modules used within the component separately, and reuse those results across multiple components. However, it does improve the runtime of command-line uses of components today, and anecdotally people are benchmarking `wasmtime run` with components.

<!--
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/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml
index d17cc3674..0f4b3bcb8 100644
--- a/.github/workflows/cargo-audit.yml
+++ b/.github/workflows/cargo-audit.yml
@@ -7,7 +7,7 @@ jobs:
     if: github.repository == 'bytecodealliance/wasmtime'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - uses: actions-rs/audit-check@v1
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b4cb5cfd7..7306556ab 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -42,7 +42,7 @@ jobs:
     name: Rustfmt
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -60,7 +60,7 @@ jobs:
     name: Clang format
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - run: git ls-files '*.h' '*.c' '*.cpp' | xargs clang-format-15 --dry-run --Werror --verbose
@@ -79,7 +79,7 @@ jobs:
     if: needs.determine.outputs.audit
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -103,7 +103,7 @@ jobs:
     if: needs.determine.outputs.audit
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -138,7 +138,7 @@ jobs:
       audit: ${{ steps.calculate.outputs.audit }}
       preview1-adapter: ${{ steps.calculate.outputs.preview1-adapter }}
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - id: calculate
       env:
         GH_TOKEN: ${{ github.token }}
@@ -202,7 +202,7 @@ jobs:
       RUSTDOCFLAGS: -Dbroken_intra_doc_links --cfg nightlydoc
       OPENVINO_SKIP_LINKING: 1
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -269,7 +269,7 @@ jobs:
     env:
       CARGO_NDK_VERSION: 2.12.2
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -351,7 +351,7 @@ jobs:
     name: Check Windows ARM64
     runs-on: windows-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -371,7 +371,7 @@ jobs:
     name: Fuzz Targets
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     # Note that building with fuzzers requires nightly since it uses unstable
@@ -410,7 +410,7 @@ jobs:
       fail-fast: ${{ github.event_name != 'pull_request' }}
       matrix: ${{ fromJson(needs.determine.outputs.test-matrix) }}
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -529,7 +529,7 @@ jobs:
     name: Test wasi-nn module
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - uses: ./.github/actions/install-rust
@@ -557,7 +557,7 @@ jobs:
       deployments: write
       contents: write
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - run: rustup update stable && rustup default stable
@@ -591,7 +591,7 @@ jobs:
     name: Run benchmarks
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -611,7 +611,7 @@ jobs:
     name: Meta deterministic check
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -629,7 +629,7 @@ jobs:
     if: github.repository == 'bytecodealliance/wasmtime' && needs.determine.outputs.run-full
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - run: rustup update stable && rustup default stable
@@ -667,7 +667,7 @@ jobs:
     env:
       CARGO_NEXTEST_VERSION: 0.9.51
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
@@ -709,7 +709,7 @@ jobs:
       fail-fast: ${{ github.event_name != 'pull_request' }}
       matrix: ${{ fromJson(needs.determine.outputs.build-matrix) }}
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
 
@@ -824,7 +824,7 @@ jobs:
       && startsWith(github.ref, 'refs/heads/release-')
       && github.repository == 'bytecodealliance/wasmtime'
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
         fetch-depth: 0
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
index 0b39524a7..f3544251c 100644
--- a/.github/workflows/performance.yml
+++ b/.github/workflows/performance.yml
@@ -74,7 +74,7 @@ jobs:
           cargo build --release
 
       - name: Checkout patch from bytecodealliance/wasmtime (pushed and triggering on this perf repo)
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: true
           path: wasmtime_commit
@@ -89,7 +89,7 @@ jobs:
           cp target/release/libwasmtime_bench_api.so /tmp/wasmtime_commit.so
 
       - name: Checkout main from bytecodealliance/wasmtime
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           ref: 'main'
           repository: 'bytecodealliance/wasmtime'
diff --git a/.github/workflows/publish-artifacts.yml b/.github/workflows/publish-artifacts.yml
index 8c149003d..191d0cc4f 100644
--- a/.github/workflows/publish-artifacts.yml
+++ b/.github/workflows/publish-artifacts.yml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     if: github.repository == 'bytecodealliance/wasmtime'
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - run: |
         sha=${{ github.sha }}
         run_id=$(
diff --git a/.github/workflows/publish-to-cratesio.yml b/.github/workflows/publish-to-cratesio.yml
index 6d05b6677..23af15a85 100644
--- a/.github/workflows/publish-to-cratesio.yml
+++ b/.github/workflows/publish-to-cratesio.yml
@@ -14,7 +14,7 @@ jobs:
     if: github.repository == 'bytecodealliance/wasmtime'
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         submodules: true
     - run: rustup update stable && rustup default stable
diff --git a/.github/workflows/release-process.yml b/.github/workflows/release-process.yml
index e2003a456..004c00f85 100644
--- a/.github/workflows/release-process.yml
+++ b/.github/workflows/release-process.yml
@@ -38,7 +38,7 @@ jobs:
     name: Run the release process
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           submodules: true
       - name: Setup
diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs
index 211b62177..0377711cd 100644
--- a/cranelift/codegen/build.rs
+++ b/cranelift/codegen/build.rs
@@ -228,6 +228,7 @@ fn get_isle_compilations(
                     src_opts.join("icmp.isle"),
                     src_opts.join("remat.isle"),
                     src_opts.join("selects.isle"),
+                    src_opts.join("spaceship.isle"),
                     src_opts.join("shifts.isle"),
                     src_opts.join("vector.isle"),
                 ],
diff --git a/cranelift/codegen/src/isa/aarch64/inst.isle b/cranelift/codegen/src/isa/aarch64/inst.isle
index 2d9309dd5..15112a585 100644
--- a/cranelift/codegen/src/isa/aarch64/inst.isle
+++ b/cranelift/codegen/src/isa/aarch64/inst.isle
@@ -1709,6 +1709,9 @@
 (decl pure partial imm_logic_from_u64 (Type u64) ImmLogic)
 (extern constructor imm_logic_from_u64 imm_logic_from_u64)
 
+(decl pure partial imm_size_from_type (Type) u16)
+(extern constructor imm_size_from_type imm_size_from_type)
+
 (decl pure partial imm_logic_from_imm64 (Type Imm64) ImmLogic)
 (extern constructor imm_logic_from_imm64 imm_logic_from_imm64)
 
@@ -2938,9 +2941,10 @@
 ;; we only match when we are zero-extending the value.
 (rule 1 (imm (integral_ty ty) (ImmExtend.Zero) k)
       (if-let n (imm_logic_from_u64 ty k))
+      (if-let m (imm_size_from_type ty))
       (add_range_fact
        (orr_imm ty (zero_reg) n)
-       64 k k))
+       m k k))
 
 (decl load_constant64_full (Type ImmExtend u64) Reg)
 (extern constructor load_constant64_full load_constant64_full)
diff --git a/cranelift/codegen/src/isa/aarch64/lower/isle.rs b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
index a70e895fb..91ea3a735 100644
--- a/cranelift/codegen/src/isa/aarch64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower/isle.rs
@@ -174,6 +174,14 @@ impl Context for IsleContext<'_, '_, MInst, AArch64Backend> {
         ImmLogic::maybe_from_u64(n, ty)
     }
 
+    fn imm_size_from_type(&mut self, ty: Type) -> Option<u16> {
+        match ty {
+            I32 => Some(32),
+            I64 => Some(64),
+            _ => None,
+        }
+    }
+
     fn imm_logic_from_imm64(&mut self, ty: Type, n: Imm64) -> Option<ImmLogic> {
         let ty = if ty.bits() < 32 { I32 } else { ty };
         self.imm_logic_from_u64(ty, n.bits() as u64)
@@ -395,7 +403,7 @@ impl Context for IsleContext<'_, '_, MInst, AArch64Backend> {
                         size,
                     });
                     if pcc {
-                        running_value = !(imm16 << shift);
+                        running_value = !(u64::from(imm.bits) << shift);
                         self.lower_ctx.add_range_fact(
                             rd.to_reg(),
                             64,
diff --git a/cranelift/codegen/src/isle_prelude.rs b/cranelift/codegen/src/isle_prelude.rs
index a2ee1563f..0ad5bfa82 100644
--- a/cranelift/codegen/src/isle_prelude.rs
+++ b/cranelift/codegen/src/isle_prelude.rs
@@ -734,18 +734,6 @@ macro_rules! isle_common_prelude_methods {
             Imm64::new((x & self.ty_mask(ty)) as i64)
         }
 
-        #[inline]
-        fn simm32(&mut self, x: Imm64) -> Option<i32> {
-            i64::from(x).try_into().ok()
-        }
-
-        #[inline]
-        fn uimm8(&mut self, x: Imm64) -> Option<u8> {
-            let x64: i64 = x.into();
-            let x8: u8 = x64.try_into().ok()?;
-            Some(x8)
-        }
-
         #[inline]
         fn offset32(&mut self, x: Offset32) -> i32 {
             x.into()
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index 7add8208e..6967aa9bc 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -562,6 +562,18 @@ macro_rules! isle_lower_prelude_methods {
             Some(value)
         }
 
+        #[inline]
+        fn simm32(&mut self, x: Imm64) -> Option<i32> {
+            i64::from(x).try_into().ok()
+        }
+
+        #[inline]
+        fn uimm8(&mut self, x: Imm64) -> Option<u8> {
+            let x64: i64 = x.into();
+            let x8: u8 = x64.try_into().ok()?;
+            Some(x8)
+        }
+
         #[inline]
         fn preg_to_reg(&mut self, preg: PReg) -> Reg {
             preg.into()
diff --git a/cranelift/codegen/src/machinst/pcc.rs b/cranelift/codegen/src/machinst/pcc.rs
index db1bf3256..d737d83da 100644
--- a/cranelift/codegen/src/machinst/pcc.rs
+++ b/cranelift/codegen/src/machinst/pcc.rs
@@ -6,8 +6,7 @@ use crate::trace;
 
 pub(crate) fn get_fact_or_default<I: VCodeInst>(vcode: &VCode<I>, reg: Reg, width: u16) -> Fact {
     trace!(
-        "get_fact_or_default: reg v{} -> {:?}",
-        reg.to_virtual_reg().unwrap().index(),
+        "get_fact_or_default: reg {reg:?} -> {:?}",
         vcode.vreg_fact(reg.into())
     );
     vcode
diff --git a/cranelift/codegen/src/opts.rs b/cranelift/codegen/src/opts.rs
index 3ec3e1165..8796b62e1 100644
--- a/cranelift/codegen/src/opts.rs
+++ b/cranelift/codegen/src/opts.rs
@@ -27,6 +27,8 @@ const MAX_ISLE_RETURNS: usize = 8;
 
 pub type ConstructorVec<T> = SmallVec<[T; MAX_ISLE_RETURNS]>;
 
+type TypeAndInstructionData = (Type, InstructionData);
+
 impl<T: smallvec::Array> generated_code::Length for SmallVec<T> {
     #[inline]
     fn len(&self) -> usize {
@@ -128,6 +130,13 @@ impl<'a, 'b, 'c> generated_code::Context for IsleContext<'a, 'b, 'c> {
         *returns = InstDataEtorIter::new(eclass);
     }
 
+    type inst_data_tupled_etor_returns = InstDataEtorIter<'a, 'b, 'c>;
+
+    fn inst_data_tupled_etor(&mut self, eclass: Value, returns: &mut InstDataEtorIter<'a, 'b, 'c>) {
+        // Literally identical to `inst_data_etor`, just a different nominal type in ISLE
+        self.inst_data_etor(eclass, returns);
+    }
+
     fn make_inst_ctor(&mut self, ty: Type, op: &InstructionData) -> Value {
         let value = self
             .ctx
@@ -149,6 +158,21 @@ impl<'a, 'b, 'c> generated_code::Context for IsleContext<'a, 'b, 'c> {
         self.ctx.func.dfg.value_type(val)
     }
 
+    fn iconst_sextend_etor(
+        &mut self,
+        (ty, inst_data): (Type, InstructionData),
+    ) -> Option<(Type, i64)> {
+        if let InstructionData::UnaryImm {
+            opcode: Opcode::Iconst,
+            imm,
+        } = inst_data
+        {
+            Some((ty, self.i64_sextend_imm64(ty, imm)))
+        } else {
+            None
+        }
+    }
+
     fn remat(&mut self, value: Value) -> Value {
         trace!("remat: {}", value);
         self.ctx.remat_values.insert(value);
diff --git a/cranelift/codegen/src/opts/arithmetic.isle b/cranelift/codegen/src/opts/arithmetic.isle
index bbb40dbf2..5ed3669e7 100644
--- a/cranelift/codegen/src/opts/arithmetic.isle
+++ b/cranelift/codegen/src/opts/arithmetic.isle
@@ -7,19 +7,30 @@
 ;; x+0 == x.
 (rule (simplify (iadd ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 ;; x-0 == x.
 (rule (simplify (isub ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 ;; 0-x == (ineg x).
 (rule (simplify (isub ty
-                      (iconst ty (u64_from_imm64 0))
+                      (iconst_u ty 0)
                       x))
       (ineg ty x))
 
+;; x + -y == -y + x == -(y - x) == x - y
+(rule (simplify (iadd ty x (ineg ty y)))
+      (isub ty x y))
+(rule (simplify (iadd ty (ineg ty y) x))
+      (isub ty x y))
+(rule (simplify (ineg ty (isub ty y x)))
+      (isub ty x y))
+;; x - -y == x + y
+(rule (simplify (isub ty x (ineg ty y)))
+      (iadd ty x y))
+
 ;; ineg(ineg(x)) == x.
 (rule (simplify (ineg ty (ineg ty x))) (subsume x))
 
@@ -36,59 +47,49 @@
       (subsume inner))
 
 ;; x-x == 0.
-(rule (simplify (isub (fits_in_64 (ty_int ty)) x x)) (subsume (iconst ty (imm64 0))))
+(rule (simplify (isub (fits_in_64 (ty_int ty)) x x)) (subsume (iconst_u ty 0)))
 
 ;; x*1 == x.
 (rule (simplify (imul ty
                       x
-                      (iconst ty (u64_from_imm64 1))))
+                      (iconst_u ty 1)))
       (subsume x))
 
 ;; x*0 == 0.
 (rule (simplify (imul ty
                       _
-                      zero @ (iconst ty (u64_from_imm64 0))))
+                      zero @ (iconst_u ty 0)))
       (subsume zero))
 
 ;; x*-1 == ineg(x).
-(rule (simplify (imul ty x (iconst ty c)))
-      (if-let -1 (i64_sextend_imm64 ty c))
+(rule (simplify (imul ty x (iconst_s ty -1)))
       (ineg ty x))
 
-;; (!x) + 1 == 1 + (!x) == !(x) - (-1) == ineg(x)
-(rule (simplify (iadd ty (bnot ty x) (iconst ty (u64_from_imm64 1))))
-      (ineg ty x))
-(rule (simplify (iadd ty (iconst ty (u64_from_imm64 1)) (bnot ty x)))
-      (ineg ty x))
-(rule (simplify (isub ty (bnot ty x) (iconst ty c)))
-      (if-let -1 (i64_sextend_imm64 ty c))
+;; (!x) + 1 == ineg(x)
+(rule (simplify (iadd ty (bnot ty x) (iconst_u ty 1)))
       (ineg ty x))
 
-;; !(x - 1) == !(x + (-1)) == !((-1) + x) == ineg(x)
-(rule (simplify (bnot ty (isub ty x (iconst ty (u64_from_imm64 1)))))
-      (ineg ty x))
-(rule (simplify (bnot ty (iadd ty x (iconst ty c))))
-      (if-let -1 (i64_sextend_imm64 ty c))
+;; !(x - 1) == !(x + (-1)) == ineg(x)
+(rule (simplify (bnot ty (isub ty x (iconst_s ty 1))))
       (ineg ty x))
-(rule (simplify (bnot ty (iadd ty (iconst ty c) x)))
-      (if-let -1 (i64_sextend_imm64 ty c))
+(rule (simplify (bnot ty (iadd ty x (iconst_s ty -1))))
       (ineg ty x))
 
 ;; x/1 == x.
 (rule (simplify (sdiv ty
                       x
-                      (iconst ty (u64_from_imm64 1))))
+                      (iconst_u ty 1)))
       (subsume x))
 (rule (simplify (udiv ty
                       x
-                      (iconst ty (u64_from_imm64 1))))
+                      (iconst_u ty 1)))
       (subsume x))
 
 ;; TODO: strength reduction: div to shifts
 ;; TODO: div/rem by constants -> magic multiplications
 
 ;; x*2 == x+x.
-(rule (simplify (imul ty x (iconst _ (simm32 2))))
+(rule (simplify (imul ty x (iconst_u _ 2)))
       (iadd ty x x))
 
 ;; x*c == x<<log2(c) when c is a power of two.
diff --git a/cranelift/codegen/src/opts/bitops.isle b/cranelift/codegen/src/opts/bitops.isle
index 6c89e0f07..a56774cec 100644
--- a/cranelift/codegen/src/opts/bitops.isle
+++ b/cranelift/codegen/src/opts/bitops.isle
@@ -1,30 +1,22 @@
 ;; Rewrites for `band`, `bnot`, `bor`, `bxor`
 
-;; x | 0 == 0 | x == x | x == x.
+;; x | 0 == x | x == x.
 (rule (simplify (bor ty
                      x
-                     (iconst ty (u64_from_imm64 0))))
-      (subsume x))
-(rule (simplify (bor ty
-                     (iconst ty (u64_from_imm64 0))
-                     x))
+                     (iconst_u ty 0)))
       (subsume x))
 (rule (simplify (bor ty x x))
       (subsume x))
 
-;; x ^ 0 == 0 ^ x == x.
+;; x ^ 0 == x.
 (rule (simplify (bxor ty
                      x
-                     (iconst ty (u64_from_imm64 0))))
-      (subsume x))
-(rule (simplify (bxor ty
-                     (iconst ty (u64_from_imm64 0))
-                     x))
+                     (iconst_u ty 0)))
       (subsume x))
 
 ;; x ^ x == 0.
 (rule (simplify (bxor (fits_in_64 (ty_int ty)) x x))
-      (subsume (iconst ty (imm64 0))))
+      (subsume (iconst_u ty 0)))
 
 ;; x ^ not(x) == not(x) ^ x == x | not(x) == not(x) | x == -1.
 ;; This identity also holds for non-integer types, vectors, and wider types.
@@ -34,20 +26,15 @@
 (rule (simplify (bor (fits_in_64 (ty_int ty)) x (bnot ty x))) (subsume (iconst ty (imm64 (ty_mask ty)))))
 (rule (simplify (bor (fits_in_64 (ty_int ty)) (bnot ty x) x)) (subsume (iconst ty (imm64 (ty_mask ty)))))
 
-;; x & -1 == -1 & x == x & x == x.
+;; x & x == x & -1 == x.
 (rule (simplify (band ty x x)) (subsume x))
-(rule (simplify (band ty x (iconst ty k)))
-      (if-let -1 (i64_sextend_imm64 ty k))
-      (subsume x))
-(rule (simplify (band ty (iconst ty k) x))
-      (if-let -1 (i64_sextend_imm64 ty k))
+(rule (simplify (band ty x (iconst_s ty -1)))
       (subsume x))
 
-;; x & 0 == 0 & x == x & not(x) == not(x) & x == 0.
-(rule (simplify (band ty _ zero @ (iconst ty (u64_from_imm64 0)))) (subsume zero))
-(rule (simplify (band ty zero @ (iconst ty (u64_from_imm64 0)) _)) (subsume zero))
-(rule (simplify (band (fits_in_64 (ty_int ty)) x (bnot ty x))) (subsume (iconst ty (imm64 0))))
-(rule (simplify (band (fits_in_64 (ty_int ty)) (bnot ty x) x)) (subsume (iconst ty (imm64 0))))
+;; x & 0 == x & not(x) == not(x) & x == 0.
+(rule (simplify (band ty _ zero @ (iconst_u ty 0))) (subsume zero))
+(rule (simplify (band (fits_in_64 (ty_int ty)) x (bnot ty x))) (subsume (iconst_u ty 0)))
+(rule (simplify (band (fits_in_64 (ty_int ty)) (bnot ty x) x)) (subsume (iconst_u ty 0)))
 
 ;; not(not(x)) == x.
 (rule (simplify (bnot ty (bnot ty x))) (subsume x))
@@ -89,8 +76,7 @@
       (bor ty x z))
 
 ;; (x ^ -1) can be replaced with the `bnot` instruction
-(rule (simplify (bxor ty x (iconst ty k)))
-  (if-let -1 (i64_sextend_imm64 ty k))
+(rule (simplify (bxor ty x (iconst_s ty -1)))
   (bnot ty x))
 
 ;; sshr((x | -x), N) == bmask(x) where N = ty_bits(ty) - 1.
@@ -120,17 +106,17 @@
 (rule (truthy (rotr _ x _)) x)
 (rule (truthy (select _ x (iconst _ (u64_from_imm64 (u64_nonzero _))) (iconst _ (u64_from_imm64 0)))) x)
 ;; (ne ty (iconst 0) v) is also canonicalized into this form via another rule
-(rule (truthy (ne _ x (iconst _ (u64_from_imm64 0)))) x)
+(rule (truthy (ne _ x (iconst_u _ 0))) x)
 
 ;; All of these expressions don't care about their input as long as it is truthy.
 ;; so we can remove expressions that preserve that property from the input.
 (rule (simplify (bmask ty v)) (if-let x (truthy v)) (bmask ty x))
 (rule (simplify (select ty v t f)) (if-let c (truthy v)) (select ty c t f))
 ;; (ne ty (iconst 0) v) is also canonicalized into this form via another rule
-(rule (simplify (ne cty v (iconst _ (u64_from_imm64 0))))
+(rule (simplify (ne cty v (iconst_u _ 0)))
       (if-let c (truthy v))
       (if-let (value_type (ty_int_ref_scalar_64 ty)) c)
-      (ne cty c (iconst ty (imm64 0))))
+      (ne cty c (iconst_u ty 0)))
 
 
 
@@ -157,42 +143,42 @@
 ;; easy way to match all of them, so only one is matched here.
 (rule (simplify (bor ty @ $I32
     (bor ty
-      (ishl ty x (iconst ty (u64_from_imm64 24)))
+      (ishl ty x (iconst_u ty 24))
       (ishl ty
-        (band ty x (iconst ty (u64_from_imm64 0xff00)))
-        (iconst ty (u64_from_imm64 8))))
+        (band ty x (iconst_u ty 0xff00))
+        (iconst_u ty 8)))
     (bor ty
       (band ty
-        (ushr ty x (iconst ty (u64_from_imm64 8)))
-        (iconst ty (u64_from_imm64 0xff00)))
-      (ushr ty x (iconst ty (u64_from_imm64 24))))))
+        (ushr ty x (iconst_u ty 8))
+        (iconst_u ty 0xff00))
+      (ushr ty x (iconst_u ty 24)))))
   (bswap ty x))
 
 (rule (simplify (bor ty @ $I64
     (bor ty
       (bor ty
-        (ishl ty x (iconst ty (u64_from_imm64 56)))
+        (ishl ty x (iconst_u ty 56))
         (ishl ty
-          (band ty x (iconst ty (u64_from_imm64 0xff00)))
-          (iconst ty (u64_from_imm64 40))))
+          (band ty x (iconst_u ty 0xff00))
+          (iconst_u ty 40)))
       (bor ty
         (ishl ty
-          (band ty x (iconst ty (u64_from_imm64 0xff_0000)))
-          (iconst ty (u64_from_imm64 24)))
+          (band ty x (iconst_u ty 0xff_0000))
+          (iconst_u ty 24))
         (ishl ty
-          (band ty x (iconst ty (u64_from_imm64 0xff00_0000)))
-          (iconst ty (u64_from_imm64 8)))))
+          (band ty x (iconst_u ty 0xff00_0000))
+          (iconst_u ty 8))))
     (bor ty
       (bor ty
         (band ty
-          (ushr ty x (iconst ty (u64_from_imm64 8)))
-          (iconst ty (u64_from_imm64 0xff00_0000)))
+          (ushr ty x (iconst_u ty 8))
+          (iconst_u ty 0xff00_0000))
         (band ty
-          (ushr ty x (iconst ty (u64_from_imm64 24)))
-          (iconst ty (u64_from_imm64 0xff_0000))))
+          (ushr ty x (iconst_u ty 24))
+          (iconst_u ty 0xff_0000)))
       (bor ty
         (band ty
-          (ushr ty x (iconst ty (u64_from_imm64 40)))
-          (iconst ty (u64_from_imm64 0xff00)))
-        (ushr ty x (iconst ty (u64_from_imm64 56)))))))
+          (ushr ty x (iconst_u ty 40))
+          (iconst_u ty 0xff00))
+        (ushr ty x (iconst_u ty 56))))))
   (bswap ty x))
diff --git a/cranelift/codegen/src/opts/cprop.isle b/cranelift/codegen/src/opts/cprop.isle
index e44404e05..a76ecdd71 100644
--- a/cranelift/codegen/src/opts/cprop.isle
+++ b/cranelift/codegen/src/opts/cprop.isle
@@ -173,6 +173,17 @@
        (select ty (iconst _ (u64_from_imm64 0)) x y))
       y)
 
+;; Replace subtraction by a "negative" constant with addition.
+;; Notably, this gives `x - (-1) == x + 1`, so other patterns don't have to
+;; match the subtract-negative-one version too.
+;; TODO: it would be nice to do this for `x + (-1) == x - 1` as well, but
+;; that needs work in lowering first to avoid regressing addressing modes.
+
+(rule (simplify (isub ty x (iconst ty k1)))
+      (if-let k2 (i64_sextend_imm64 ty k1))
+      (if-let $true (u64_lt (i64_as_u64 (i64_neg k2)) (i64_as_u64 k2)))
+      (iadd ty x (iconst ty (imm64_masked ty (i64_as_u64 (i64_neg k2))))))
+
 ;; TODO: fadd, fsub, fmul, fdiv, fneg, fabs
 
 ;; A splat of a constant can become a direct `vconst` with the appropriate bit
diff --git a/cranelift/codegen/src/opts/extends.isle b/cranelift/codegen/src/opts/extends.isle
index a1a4dfda0..dd45c1954 100644
--- a/cranelift/codegen/src/opts/extends.isle
+++ b/cranelift/codegen/src/opts/extends.isle
@@ -4,6 +4,14 @@
 (rule (simplify (sextend ty (sextend _intermediate_ty x)))
       (sextend ty x))
 
+;; Once something has be `uextend`ed, further `sextend`ing is the same as `uextend`
+(rule (simplify (sextend ty (uextend _intermediate_ty x)))
+      (uextend ty x))
+
+;; `icmp` is zero or one, so sign-extending is the same as zero-extending
+(rule (simplify (sextend ty x@(icmp _ _ _ _)))
+      (uextend ty x))
+
 ;; Masking out any of the top bits of the result of `uextend` is a no-op. (This
 ;; is like a cheap version of known-bits analysis.)
 (rule (simplify (band wide x @ (uextend _ (value_type narrow)) (iconst _ (u64_from_imm64 mask))))
@@ -20,13 +28,22 @@
 (rule (simplify
        (slt ty
              (uextend $I64 x @ (value_type $I32))
-             (iconst _ (u64_from_imm64 0))))
-      (iconst ty (imm64 0)))
+             (iconst_u _ 0)))
+      (iconst_u ty 0))
 (rule (simplify
        (sge ty
              (uextend $I64 x @ (value_type $I32))
-             (iconst _ (u64_from_imm64 0))))
-      (iconst ty (imm64 1)))
+             (iconst_u _ 0)))
+      (iconst_u ty 1))
+
+;; Sign-extending can't change whether a number is zero nor how it signed-compares to zero
+(rule (simplify (eq _ (sextend _ x@(value_type ty)) (iconst_s _ 0)))
+      (eq ty x (iconst_s ty 0)))
+(rule (simplify (ne _ (sextend _ x@(value_type ty)) (iconst_s _ 0)))
+      (ne ty x (iconst_s ty 0)))
+(rule (simplify (icmp _ cc (sextend _ x@(value_type ty)) (iconst_s _ 0)))
+      (if (signed_cond_code cc))
+      (icmp ty cc x (iconst_s ty 0)))
 
 ;; A reduction-of-an-extend back to the same original type is the same as not
 ;; actually doing the extend in the first place.
diff --git a/cranelift/codegen/src/opts/icmp.isle b/cranelift/codegen/src/opts/icmp.isle
index b95d978c6..bdc868162 100644
--- a/cranelift/codegen/src/opts/icmp.isle
+++ b/cranelift/codegen/src/opts/icmp.isle
@@ -2,26 +2,26 @@
 
 ;; `x == x` is always true for integers; `x != x` is false. Strict
 ;; inequalities are false, and loose inequalities are true.
-(rule (simplify (eq  (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 1)))
-(rule (simplify (ne  (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 0)))
-(rule (simplify (ugt (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 0)))
-(rule (simplify (uge (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 1)))
-(rule (simplify (sgt (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 0)))
-(rule (simplify (sge (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 1)))
-(rule (simplify (ult (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 0)))
-(rule (simplify (ule (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 1)))
-(rule (simplify (slt (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 0)))
-(rule (simplify (sle (fits_in_64 (ty_int ty)) x x)) (iconst ty (imm64 1)))
+(rule (simplify (eq  (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 1))
+(rule (simplify (ne  (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 0))
+(rule (simplify (ugt (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 0))
+(rule (simplify (uge (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 1))
+(rule (simplify (sgt (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 0))
+(rule (simplify (sge (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 1))
+(rule (simplify (ult (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 0))
+(rule (simplify (ule (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 1))
+(rule (simplify (slt (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 0))
+(rule (simplify (sle (fits_in_64 (ty_int ty)) x x)) (iconst_u ty 1))
 
 ;; Optimize icmp-of-icmp.
 (rule (simplify (ne ty
                       (uextend _ inner @ (icmp ty _ _ _))
-                      (iconst _ (u64_from_imm64 0))))
+                      (iconst_u _ 0)))
       (subsume inner))
 
 (rule (simplify (eq ty
                       (uextend _ (icmp ty cc x y))
-                      (iconst _ (u64_from_imm64 0))))
+                      (iconst_u _ 0)))
       (subsume (icmp ty (intcc_complement cc) x y)))
 
 ;; Optimize select-of-uextend-of-icmp to select-of-icmp, because
@@ -39,30 +39,30 @@
 (rule (simplify
        (band (ty_int _)
              cmp @ (icmp _ _ _ _)
-             (iconst _ (u64_from_imm64 1))))
+             (iconst_u _ 1)))
       cmp)
 (rule (simplify
        (band (ty_int _)
              extend @ (uextend _ (icmp _ _ _ _))
-             (iconst _ (u64_from_imm64 1))))
+             (iconst_u _ 1)))
       extend)
 
 ;; Comparisons against largest/smallest signed/unsigned values:
 ;; ult(x, 0) == false.
-(rule (simplify (ult (fits_in_64 (ty_int bty)) x zero @ (iconst _ (u64_from_imm64 0))))
-      (subsume (iconst bty (imm64 0))))
+(rule (simplify (ult (fits_in_64 (ty_int bty)) x zero @ (iconst_u _ 0)))
+      (subsume (iconst_u bty 0)))
 
 ;; ule(x, 0) == eq(x, 0)
-(rule (simplify (ule (fits_in_64 (ty_int bty)) x zero @ (iconst _ (u64_from_imm64 0))))
+(rule (simplify (ule (fits_in_64 (ty_int bty)) x zero @ (iconst_u _ 0)))
       (eq bty x zero))
 
 ;; ugt(x, 0) == ne(x, 0).
-(rule (simplify (ugt (fits_in_64 (ty_int bty)) x zero @ (iconst _ (u64_from_imm64 0))))
+(rule (simplify (ugt (fits_in_64 (ty_int bty)) x zero @ (iconst_u _ 0)))
       (ne bty x zero))
 
 ;; uge(x, 0) == true.
-(rule (simplify (uge (fits_in_64 (ty_int bty)) x zero @ (iconst _ (u64_from_imm64 0))))
-      (subsume (iconst bty (imm64 1))))
+(rule (simplify (uge (fits_in_64 (ty_int bty)) x zero @ (iconst_u _ 0)))
+      (subsume (iconst_u bty 1)))
 
 ;; ult(x, UMAX) == ne(x, UMAX).
 (rule (simplify (ult (fits_in_64 (ty_int bty)) x umax @ (iconst cty (u64_from_imm64 y))))
@@ -72,12 +72,12 @@
 ;; ule(x, UMAX) == true.
 (rule (simplify (ule (fits_in_64 (ty_int bty)) x umax @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_umax cty)))
-       (subsume (iconst bty (imm64 1))))
+       (subsume (iconst_u bty 1)))
 
 ;; ugt(x, UMAX) == false.
 (rule (simplify (ugt (fits_in_64 (ty_int bty)) x umax @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_umax cty)))
-       (subsume (iconst bty (imm64 0))))
+       (subsume (iconst_u bty 0)))
 
 ;; uge(x, UMAX) == eq(x, UMAX).
 (rule (simplify (uge (fits_in_64 (ty_int bty)) x umax @ (iconst cty (u64_from_imm64 y))))
@@ -87,7 +87,7 @@
 ;; slt(x, SMIN) == false.
 (rule (simplify (slt (fits_in_64 (ty_int bty)) x smin @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_smin cty)))
-       (subsume (iconst bty (imm64 0))))
+       (subsume (iconst_u bty 0)))
 
 ;; sle(x, SMIN) == eq(x, SMIN).
 (rule (simplify (sle (fits_in_64 (ty_int bty)) x smin @ (iconst cty (u64_from_imm64 y))))
@@ -102,7 +102,7 @@
 ;; sge(x, SMIN) == true.
 (rule (simplify (sge (fits_in_64 (ty_int bty)) x smin @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_smin cty)))
-       (subsume (iconst bty (imm64 1))))
+       (subsume (iconst_u bty 1)))
 
 ;; slt(x, SMAX) == ne(x, SMAX).
 (rule (simplify (slt (fits_in_64 (ty_int bty)) x smax @ (iconst cty (u64_from_imm64 y))))
@@ -112,12 +112,12 @@
 ;; sle(x, SMAX) == true.
 (rule (simplify (sle (fits_in_64 (ty_int bty)) x smax @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_smax cty)))
-       (subsume (iconst bty (imm64 1))))
+       (subsume (iconst_u bty 1)))
 
 ;; sgt(x, SMAX) == false.
 (rule (simplify (sgt (fits_in_64 (ty_int bty)) x smax @ (iconst cty (u64_from_imm64 y))))
        (if-let $true (u64_eq y (ty_smax cty)))
-       (subsume (iconst bty (imm64 0))))
+       (subsume (iconst_u bty 0)))
 
 ;; sge(x, SMAX) == eq(x, SMAX).
 (rule (simplify (sge (fits_in_64 (ty_int bty)) x smax @ (iconst cty (u64_from_imm64 y))))
@@ -135,25 +135,23 @@
 
 ;; Prefer comparing against zero
 ;; uge(x, 1) == ne(x, 0)
-(rule (simplify (uge ty x (iconst cty (u64_from_imm64 1))))
-      (ne ty x (iconst cty (imm64 0))))
+(rule (simplify (uge ty x (iconst_u cty 1)))
+      (ne ty x (iconst_u cty 0)))
 ;; ult(x, 1) == eq(x, 0)
-(rule (simplify (ult ty x (iconst cty (u64_from_imm64 1))))
-      (eq ty x (iconst cty (imm64 0))))
+(rule (simplify (ult ty x (iconst_u cty 1)))
+      (eq ty x (iconst_u cty 0)))
 ;; sge(x, 1) == sgt(x, 0)
-(rule (simplify (sge ty x (iconst cty (u64_from_imm64 1))))
-      (sgt ty x (iconst cty (imm64 0))))
+(rule (simplify (sge ty x (iconst_s cty 1)))
+      (sgt ty x (iconst_s cty 0)))
 ;; slt(x, 1) == sle(x, 0)
-(rule (simplify (slt ty x (iconst cty (u64_from_imm64 1))))
-      (sle ty x (iconst cty (imm64 0))))
+(rule (simplify (slt ty x (iconst_s cty 1)))
+      (sle ty x (iconst_s cty 0)))
 ;; sgt(x, -1) == sge(x, 0)
-(rule (simplify (sgt ty x (iconst cty c)))
-      (if-let -1 (i64_sextend_imm64 cty c))
-      (sge ty x (iconst cty (imm64 0))))
+(rule (simplify (sgt ty x (iconst_s cty -1)))
+      (sge ty x (iconst_s cty 0)))
 ;; sle(x, -1) == slt(x, 0)
-(rule (simplify (sle ty x (iconst cty c)))
-      (if-let -1 (i64_sextend_imm64 cty c))
-      (slt ty x (iconst cty (imm64 0))))
+(rule (simplify (sle ty x (iconst_s cty -1)))
+      (slt ty x (iconst_s cty 0)))
 
 (decl pure partial intcc_comparable (IntCC IntCC) bool)
 (rule (intcc_comparable x y)
@@ -173,7 +171,7 @@
 (rule (decompose_intcc (IntCC.NotEqual)) 6)
 
 (decl compose_icmp (Type u64 bool Value Value) Value)
-(rule (compose_icmp ty 0 _ _ _) (subsume (iconst ty (imm64 0))))
+(rule (compose_icmp ty 0 _ _ _) (subsume (iconst_u ty 0)))
 (rule (compose_icmp ty 1 _ x y) (icmp ty (IntCC.Equal) x y))
 (rule (compose_icmp ty 2 $false x y) (icmp ty (IntCC.UnsignedLessThan) x y))
 (rule (compose_icmp ty 2 $true x y) (icmp ty (IntCC.SignedLessThan) x y))
@@ -184,7 +182,7 @@
 (rule (compose_icmp ty 5 $false x y) (icmp ty (IntCC.UnsignedGreaterThanOrEqual) x y))
 (rule (compose_icmp ty 5 $true x y) (icmp ty (IntCC.SignedGreaterThanOrEqual) x y))
 (rule (compose_icmp ty 6 _ x y) (icmp ty (IntCC.NotEqual) x y))
-(rule (compose_icmp ty 7 _ _ _) (subsume (iconst ty (imm64 1))))
+(rule (compose_icmp ty 7 _ _ _) (subsume (iconst_u ty 1)))
 
 (decl pure intcc_class (IntCC) u64)
 (rule (intcc_class (IntCC.UnsignedLessThan)) 1)
diff --git a/cranelift/codegen/src/opts/selects.isle b/cranelift/codegen/src/opts/selects.isle
index 052a7b59c..e966fb3ec 100644
--- a/cranelift/codegen/src/opts/selects.isle
+++ b/cranelift/codegen/src/opts/selects.isle
@@ -6,13 +6,13 @@
 
 ;; if icmp(x, y) { 1 } else { 0 } => uextend(icmp(x, y))
 (rule (simplify (select ty cmp@(icmp _ cc x y)
-                        (iconst _ (u64_from_imm64 1))
-                        (iconst _ (u64_from_imm64 0))))
+                        (iconst_u _ 1)
+                        (iconst_u _ 0)))
     (uextend_from_i8 ty cmp))
 ;; if icmp(x, y) { 0 } else { 1 } => uextend(!icmp(x, y))
 (rule (simplify (select sty (icmp cty cc x y)
-                        (iconst _ (u64_from_imm64 0))
-                        (iconst _ (u64_from_imm64 1))))
+                        (iconst_u _ 0)
+                        (iconst_u _ 1)))
     (uextend_from_i8 sty (icmp cty (intcc_complement cc) x y)))
 
 ;; Transform select-of-icmp into {u,s}{min,max} instructions where possible.
diff --git a/cranelift/codegen/src/opts/shifts.isle b/cranelift/codegen/src/opts/shifts.isle
index 272c5c90b..0849d541e 100644
--- a/cranelift/codegen/src/opts/shifts.isle
+++ b/cranelift/codegen/src/opts/shifts.isle
@@ -3,23 +3,23 @@
 ;; x>>0 == x<<0 == x rotr 0 == x rotl 0 == x.
 (rule (simplify (ishl ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 (rule (simplify (ushr ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 (rule (simplify (sshr ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 (rule (simplify (rotr ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 (rule (simplify (rotl ty
                       x
-                      (iconst ty (u64_from_imm64 0))))
+                      (iconst_u ty 0)))
       (subsume x))
 
 ;; `(x >> k) << k` is the same as masking off the bottom `k` bits (regardless if
@@ -159,7 +159,7 @@
                               (u64_and k1 (ty_shift_mask ty))
                               (u64_and k2 (ty_shift_mask ty))))
       (if-let $true (u64_lt shift_amt (ty_bits_u64 (lane_type ty))))
-      (ishl ty x (iconst_u64 kty shift_amt)))
+      (ishl ty x (iconst_u kty shift_amt)))
 
 (rule (simplify (ushr ty
                       (ushr ty x (iconst kty (u64_from_imm64 k1)))
@@ -168,7 +168,7 @@
                               (u64_and k1 (ty_shift_mask ty))
                               (u64_and k2 (ty_shift_mask ty))))
       (if-let $true (u64_lt shift_amt (ty_bits_u64 (lane_type ty))))
-      (ushr ty x (iconst_u64 kty shift_amt)))
+      (ushr ty x (iconst_u kty shift_amt)))
 
 (rule (simplify (sshr ty
                       (sshr ty x (iconst kty (u64_from_imm64 k1)))
@@ -177,7 +177,7 @@
                               (u64_and k1 (ty_shift_mask ty))
                               (u64_and k2 (ty_shift_mask ty))))
       (if-let $true (u64_lt shift_amt (ty_bits_u64 (lane_type ty))))
-      (sshr ty x (iconst_u64 kty shift_amt)))
+      (sshr ty x (iconst_u kty shift_amt)))
 
 ;; Simliarly, if the shift amount overflows the type, then we can turn
 ;; it into a 0
@@ -191,7 +191,7 @@
                               (u64_and k1 (ty_shift_mask ty))
                               (u64_and k2 (ty_shift_mask ty))))
       (if-let $true (u64_le (ty_bits_u64 ty) shift_amt))
-      (subsume (iconst_u64 ty 0)))
+      (subsume (iconst_u ty 0)))
 
 (rule (simplify (ushr ty
                       (ushr ty x (iconst _ (u64_from_imm64 k1)))
@@ -200,7 +200,7 @@
                               (u64_and k1 (ty_shift_mask ty))
                               (u64_and k2 (ty_shift_mask ty))))
       (if-let $true (u64_le (ty_bits_u64 ty) shift_amt))
-      (subsume (iconst_u64 ty 0)))
+      (subsume (iconst_u ty 0)))
 
 ;; (rotl (rotr x y) y) == x
 ;; (rotr (rotl x y) y) == x
diff --git a/cranelift/codegen/src/opts/spaceship.isle b/cranelift/codegen/src/opts/spaceship.isle
new file mode 100644
index 000000000..d7d207152
--- /dev/null
+++ b/cranelift/codegen/src/opts/spaceship.isle
@@ -0,0 +1,213 @@
+;; Simplifications for C++20's `<=>` "spaceship" operator, aka Rust's `Ord::cmp`.
+;;
+;; There's no cranelift instruction for this, nor usually a machine instruction.
+;; Inspired by <https://graphics.stanford.edu/~seander/bithacks.html#CopyIntegerSign>
+;; we canonicalize the various implementations of `x <=> y` to `(x > y) - (x < y)`.
+
+;; Unfortunately, there's at least 3!×2 reasonable ways to write this as nested
+;; selects, and no broad agreement which is the best -- notably Rust 1.74 and
+;; Clang 17 use different sequences -- so we just match all of them.
+
+;; x < y ? -1 : x == y ? 0 : +1
+;; x < y ? -1 : x != y ? +1 : 0
+(rule (simplify (select ty (ult rty x y)
+                           (iconst_s ty -1)
+                           (ne rty x y)))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+(rule (simplify (select ty (ult rty x y)
+                           (iconst_s ty -1)
+                           (uextend ty (ne rty x y))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x < y ? -1 : x <= y ? 0 : +1
+;; x < y ? -1 : x > y ? +1 : 0
+(rule (simplify (select ty (ult rty x y)
+                           (iconst_s ty -1)
+                           (ugt rty x y)))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+(rule (simplify (select ty (ult rty x y)
+                           (iconst_s ty -1)
+                           (uextend ty (ugt rty x y))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+
+;; x == y ? 0 : x < y ? -1 : +1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (ult rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x == y ? 0 : x <= y ? -1 : +1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (ule rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x == y ? 0 : x > y ? +1 : -1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (ugt rty x y)
+                                      (iconst_s ty 1)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x == y ? 0 : x >= y ? +1 : -1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (uge rty x y)
+                                      (iconst_s ty 1)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+
+;; x > y ? 1 : x < y ? -1 : 0
+(rule (simplify (select ty (ugt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (ult rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 0))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x > y ? 1 : x != y ? -1 : 0
+(rule (simplify (select ty (ugt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (ne rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 0))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x > y ? 1 : x == y ? 0 : -1
+(rule (simplify (select ty (ugt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (eq rty x y)
+                                      (iconst_s ty 0)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+;; x > y ? 1 : x >= y ? 0 : -1
+(rule (simplify (select ty (ugt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (uge rty x y)
+                                      (iconst_s ty 0)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+
+;; Same, but for signed comparisons this time
+
+;; x < y ? -1 : x == y ? 0 : +1
+;; x < y ? -1 : x != y ? +1 : 0
+(rule (simplify (select ty (slt rty x y)
+                           (iconst_s ty -1)
+                           (ne rty x y)))
+      (spaceship_s rty x y))
+(rule (simplify (select ty (slt rty x y)
+                           (iconst_s ty -1)
+                           (uextend ty (ne rty x y))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x < y ? -1 : x <= y ? 0 : +1
+;; x < y ? -1 : x > y ? +1 : 0
+(rule (simplify (select ty (slt rty x y)
+                           (iconst_s ty -1)
+                           (sgt rty x y)))
+      (spaceship_s rty x y))
+(rule (simplify (select ty (slt rty x y)
+                           (iconst_s ty -1)
+                           (uextend ty (sgt rty x y))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+
+;; x == y ? 0 : x < y ? -1 : +1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (slt rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x == y ? 0 : x <= y ? -1 : +1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (sle rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x == y ? 0 : x > y ? +1 : -1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (sgt rty x y)
+                                      (iconst_s ty 1)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x == y ? 0 : x >= y ? +1 : -1
+(rule (simplify (select ty (eq rty x y)
+                           (iconst_s ty 0)
+                           (select ty (sge rty x y)
+                                      (iconst_s ty 1)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+
+;; x > y ? 1 : x < y ? -1 : 0
+(rule (simplify (select ty (sgt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (slt rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 0))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x > y ? 1 : x != y ? -1 : 0
+(rule (simplify (select ty (sgt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (ne rty x y)
+                                      (iconst_s ty -1)
+                                      (iconst_s ty 0))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x > y ? 1 : x == y ? 0 : -1
+(rule (simplify (select ty (sgt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (eq rty x y)
+                                      (iconst_s ty 0)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+;; x > y ? 1 : x >= y ? 0 : -1
+(rule (simplify (select ty (sgt rty x y)
+                           (iconst_s ty 1)
+                           (select ty (sge rty x y)
+                                      (iconst_s ty 0)
+                                      (iconst_s ty -1))))
+      (sextend_from_i8 ty (spaceship_s rty x y)))
+
+;; Then once we have it normalized, we can apply some basic simplifications.
+;; For example, a derived `PartialOrd::lt` on a newtype in Rust will essentially
+;; emit `(a <=> b) < 0`, and replacing that with `a < b` can really help.
+;; `icmp.isle` prefers comparing against zero so we don't need to worry about
+;; also matching things like `(a <=> b) < 1` or `(a <=> b) <= -1`.
+
+;; (a <=> b) == 0 --> a == b
+(rule (simplify (eq _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (eq ty x y))
+(rule (simplify (eq _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (eq ty x y))
+;; (a <=> b) != 0 --> a != b
+(rule (simplify (ne _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (ne ty x y))
+(rule (simplify (ne _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (ne ty x y))
+
+;; (a <=> b) < 0 --> a < b
+(rule (simplify (slt _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (slt ty x y))
+(rule (simplify (slt _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (ult ty x y))
+;; (a <=> b) <= 0 --> a <= b
+(rule (simplify (sle _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (sle ty x y))
+(rule (simplify (sle _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (ule ty x y))
+;; (a <=> b) > 0 --> a > b
+(rule (simplify (sgt _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (sgt ty x y))
+(rule (simplify (sgt _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (ugt ty x y))
+;; (a <=> b) >= 0 --> a >= b
+(rule (simplify (sge _ (spaceship_s ty x y) (iconst_s _ 0)))
+      (sge ty x y))
+(rule (simplify (sge _ (spaceship_u ty x y) (iconst_s _ 0)))
+      (uge ty x y))
+
+;; extend from i8 to i8 is invalid CLIF, so this allows fixing that in the output
+;; rather than needing to duplicate rules for the different width categories
+(decl sextend_from_i8 (Type Value) Value)
+(rule 0 (sextend_from_i8 ty val) (sextend ty val))
+(rule 1 (sextend_from_i8 $I8 val) val)
diff --git a/cranelift/codegen/src/prelude.isle b/cranelift/codegen/src/prelude.isle
index c4416a4f9..ed625559f 100644
--- a/cranelift/codegen/src/prelude.isle
+++ b/cranelift/codegen/src/prelude.isle
@@ -67,14 +67,6 @@
 (decl pure partial u8_lt (u8 u8) Unit)
 (extern constructor u8_lt u8_lt)
 
-;; Get a signed 32-bit immediate in an u32 from an Imm64, if possible.
-(decl simm32 (i32) Imm64)
-(extern extractor simm32 simm32)
-
-;; Get an unsigned 8-bit immediate in a u8 from an Imm64, if possible.
-(decl uimm8 (u8) Imm64)
-(extern extractor uimm8 uimm8)
-
 ;;;; Primitive Type Conversions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (decl pure u8_as_i8 (u8) i8)
diff --git a/cranelift/codegen/src/prelude_lower.isle b/cranelift/codegen/src/prelude_lower.isle
index d90286e88..54851de65 100644
--- a/cranelift/codegen/src/prelude_lower.isle
+++ b/cranelift/codegen/src/prelude_lower.isle
@@ -302,6 +302,18 @@
 (decl maybe_uextend (Value) Value)
 (extern extractor maybe_uextend maybe_uextend)
 
+;; Get a signed 32-bit immediate in an u32 from an Imm64, if possible.
+;; Note that this checks that the raw i64 value from the Imm64 fits in i32,
+;; so `-1_u32` will not actually match -- it's treated as `0xFFFF_FFFF_i64`,
+;; which doesn't fit in an i32 and thus doesn't match the extractor.
+;; An Imm64 of `-1_i64` *will* match, however.
+(decl simm32 (i32) Imm64)
+(extern extractor simm32 simm32)
+
+;; Get an unsigned 8-bit immediate in a u8 from an Imm64, if possible.
+(decl uimm8 (u8) Imm64)
+(extern extractor uimm8 uimm8)
+
 ;; Instruction creation helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;; Emit an instruction.
diff --git a/cranelift/codegen/src/prelude_opt.isle b/cranelift/codegen/src/prelude_opt.isle
index 921c7c65b..5011481af 100644
--- a/cranelift/codegen/src/prelude_opt.isle
+++ b/cranelift/codegen/src/prelude_opt.isle
@@ -6,6 +6,14 @@
 (decl multi inst_data (Type InstructionData) Value)
 (extern extractor inst_data inst_data_etor)
 
+;; Identical to `inst_data`, just with a different ISLE type.
+;; This is basically a manual version of `curry`/`uncurry` in Haskell:
+;; to compose extractors the outer one needs to be single-parameter,
+;; so this combines the two parameters of `inst_data` into one.
+(type TypeAndInstructionData (primitive TypeAndInstructionData))
+(decl multi inst_data_tupled (TypeAndInstructionData) Value)
+(extern extractor inst_data_tupled inst_data_tupled_etor)
+
 ;; Construct a pure node, returning a new (or deduplicated
 ;; already-existing) eclass ID.
 (decl make_inst (Type InstructionData) Value)
@@ -28,6 +36,14 @@
 (rule (sgt ty x y) (icmp ty (IntCC.SignedGreaterThan) x y))
 (rule (sge ty x y) (icmp ty (IntCC.SignedGreaterThanOrEqual) x y))
 
+;; 3-way comparison, returning -1/0/+1 in I8
+(decl spaceship_s (Type Value Value) Value)
+(rule (spaceship_s ty x y) (isub $I8 (sgt ty x y) (slt ty x y)))
+(extractor (spaceship_s ty x y) (isub $I8 (sgt ty x y) (slt ty x y)))
+(decl spaceship_u (Type Value Value) Value)
+(rule (spaceship_u ty x y) (isub $I8 (ugt ty x y) (ult ty x y)))
+(extractor (spaceship_u ty x y) (isub $I8 (ugt ty x y) (ult ty x y)))
+
 ;;;;; optimization toplevel ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;; The main matcher rule invoked by the toplevel driver.
@@ -46,13 +62,33 @@
 
 ;;;;; constructors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(decl iconst_u64 (Type u64) Value)
+(decl iconst_sextend_etor (Type i64) TypeAndInstructionData)
+(extern extractor iconst_sextend_etor iconst_sextend_etor)
 
-;; Use a single iconst for types that fit in 64 bits.
-(rule 0 (iconst_u64 (ty_int_ref_scalar_64 ty) val)
-    (if-let $true (u64_le val (ty_umax ty)))
-    (iconst ty (imm64_masked ty val)))
+;; Construct an `iconst` from an `i64` or Extract an `i64` from an `iconst`
+;; by treating the constant as signed.
+;; When extracting, smaller types get their value sign-extended to 64-bits,
+;; so that `iconst.i8 255` will give you a `-1_i64`.
+;; When constructing, the rule will fail if the value cannot be represented in
+;; the target type.  If it fits, it'll be masked accordingly in the constant.
+(decl iconst_s (Type i64) Value)
+(extractor (iconst_s ty c) (inst_data_tupled (iconst_sextend_etor ty c)))
+(rule 0 (iconst_s ty c)
+	(if-let c_masked (u64_and (i64_as_u64 c) (ty_umax ty)))
+	(if-let c_reextended (i64_sextend_u64 ty c_masked))
+	(if-let $true (u64_eq (i64_as_u64 c) (i64_as_u64 c_reextended)))
+	(iconst ty (imm64 c_masked)))
+(rule 1 (iconst_s $I128 c) (sextend $I128 (iconst_s $I64 c)))
 
-;; For i128 types use a iconst, but zero extend it to i128.
-(rule 1 (iconst_u64 $I128 val)
-    (uextend $I128 (iconst $I64 (imm64_masked $I64 val))))
+;; Construct an `iconst` from a `u64` or Extract a `u64` from an `iconst`
+;; by treating the constant as unsigned.
+;; When extracting, smaller types get their value zero-extended to 64-bits,
+;; so that `iconst.i8 255` will give you a `255_u64`.
+;; When constructing, the rule will fail if the value cannot be represented in
+;; the target type.
+(decl iconst_u (Type u64) Value)
+(extractor (iconst_u ty c) (iconst ty (u64_from_imm64 c)))
+(rule 0 (iconst_u ty c)
+	(if-let $true (u64_le c (ty_umax ty)))
+    (iconst ty (imm64 c)))
+(rule 1 (iconst_u $I128 c) (uextend $I128 (iconst_u $I64 c)))
diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs
index f934e71ae..74ef67857 100644
--- a/cranelift/isle/islec/src/main.rs
+++ b/cranelift/isle/islec/src/main.rs
@@ -12,11 +12,11 @@ use std::{
 struct Opts {
     /// The output file to write the generated Rust code to. `stdout` is used if
     /// this is not given.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 
     /// The input ISLE DSL source files.
-    #[clap(required = true)]
+    #[arg(required = true)]
     inputs: Vec<PathBuf>,
 }
 
diff --git a/cranelift/serde/src/clif-json.rs b/cranelift/serde/src/clif-json.rs
index 572a9df10..cd1f7b5b0 100644
--- a/cranelift/serde/src/clif-json.rs
+++ b/cranelift/serde/src/clif-json.rs
@@ -37,12 +37,12 @@ fn call_de(file: &File) -> Result<(), String> {
 
 /// Cranelift JSON serializer/deserializer utility
 #[derive(Parser, Debug)]
-#[clap(about)]
+#[command(about)]
 enum Args {
     /// Serializes Cranelift IR into JSON
     Serialize {
         /// Generate pretty json
-        #[clap(long, short)]
+        #[arg(long, short)]
         pretty: bool,
 
         /// Input file for serialization
diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs
index d7a523432..57bb5e5c8 100644
--- a/cranelift/src/bugpoint.rs
+++ b/cranelift/src/bugpoint.rs
@@ -26,14 +26,14 @@ pub struct Options {
     file: PathBuf,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the target architecture.
     target: String,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/cat.rs b/cranelift/src/cat.rs
index 167d768aa..2c48318fb 100644
--- a/cranelift/src/cat.rs
+++ b/cranelift/src/cat.rs
@@ -13,7 +13,7 @@ use std::path::{Path, PathBuf};
 #[derive(Parser)]
 pub struct Options {
     /// Specify input file(s) to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs
index 79ea48668..12b740e09 100644
--- a/cranelift/src/clif-util.rs
+++ b/cranelift/src/clif-util.rs
@@ -43,15 +43,15 @@ enum Commands {
 #[derive(Parser)]
 struct TestOptions {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print pass timing report for test
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     time_passes: bool,
 
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
@@ -59,11 +59,11 @@ struct TestOptions {
 #[derive(Parser)]
 struct PassOptions {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print pass timing report for test
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     time_passes: bool,
 
     /// Specify an input file to be used. Use '-' for stdin.
@@ -73,7 +73,7 @@ struct PassOptions {
     target: String,
 
     /// Specify pass(es) to be run on the input file
-    #[clap(required = true)]
+    #[arg(required = true)]
     passes: Vec<String>,
 }
 
diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs
index 0df3f3fa6..782484abd 100644
--- a/cranelift/src/compile.rs
+++ b/cranelift/src/compile.rs
@@ -17,30 +17,30 @@ use std::path::PathBuf;
 #[derive(Parser)]
 pub struct Options {
     /// Print the resulting Cranelift IR
-    #[clap(short)]
+    #[arg(short)]
     print: bool,
 
     /// Print pass timing report
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     report_times: bool,
 
     /// Print machine code disassembly
-    #[clap(short = 'D', long)]
+    #[arg(short = 'D', long)]
     disasm: bool,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Specify an input file to be used. Use '-' for stdin.
     files: Vec<PathBuf>,
 
     /// Output object file
-    #[clap(short = 'o', long = "output")]
+    #[arg(short = 'o', long = "output")]
     output: Option<PathBuf>,
 }
 
diff --git a/cranelift/src/interpret.rs b/cranelift/src/interpret.rs
index a752d692d..cd4f50a62 100644
--- a/cranelift/src/interpret.rs
+++ b/cranelift/src/interpret.rs
@@ -14,11 +14,11 @@ use thiserror::Error;
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/print_cfg.rs b/cranelift/src/print_cfg.rs
index fa7027c42..cadb61c76 100644
--- a/cranelift/src/print_cfg.rs
+++ b/cranelift/src/print_cfg.rs
@@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 }
 
diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs
index 5564736e3..1264dfb3b 100644
--- a/cranelift/src/run.rs
+++ b/cranelift/src/run.rs
@@ -14,11 +14,11 @@ use target_lexicon::Triple;
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    #[clap(required = true)]
+    #[arg(required = true)]
     files: Vec<PathBuf>,
 
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 }
 
diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs
index a8490f842..f1d1c11f3 100644
--- a/cranelift/src/souper_harvest.rs
+++ b/cranelift/src/souper_harvest.rs
@@ -22,21 +22,21 @@ pub struct Options {
 
     /// Specify the directory where harvested left-hand side files should be
     /// written to.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output_dir: PathBuf,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Add a comment from which CLIF variable and function each left-hand side
     /// was harvested from. This prevents deduplicating harvested left-hand
     /// sides.
-    #[clap(long)]
+    #[arg(long)]
     add_harvest_source: bool,
 }
 
@@ -56,7 +56,7 @@ pub fn run(options: &Options) -> Result<()> {
         ))
     };
 
-    match std::fs::create_dir_all(&options.output_dir) {
+    match fs::create_dir_all(&options.output_dir) {
         Ok(_) => {}
         Err(e)
             if e.kind() == io::ErrorKind::AlreadyExists
diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs
index 8a5e2102b..d1eb99c7b 100644
--- a/cranelift/src/wasm.rs
+++ b/cranelift/src/wasm.rs
@@ -59,46 +59,46 @@ macro_rules! vcprint {
 #[derive(Parser)]
 pub struct Options {
     /// Be more verbose
-    #[clap(short, long)]
+    #[arg(short, long)]
     verbose: bool,
 
     /// Print the resulting Cranelift IR
-    #[clap(short)]
+    #[arg(short)]
     print: bool,
 
     /// Print pass timing report
-    #[clap(short = 'T')]
+    #[arg(short = 'T')]
     report_times: bool,
 
     /// Print machine code disassembly
-    #[clap(short = 'D', long)]
+    #[arg(short = 'D', long)]
     disasm: bool,
 
     /// Configure Cranelift settings
-    #[clap(long = "set")]
+    #[arg(long = "set")]
     settings: Vec<String>,
 
     /// Specify the Cranelift target
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 
     /// Specify an input file to be used. Use '-' for stdin.
     files: Vec<PathBuf>,
 
     /// Print bytecode size
-    #[clap(short = 'X')]
+    #[arg(short = 'X')]
     print_size: bool,
 
     /// Just decode Wasm into Cranelift IR, don't compile it to native code
-    #[clap(short = 't')]
+    #[arg(short = 't')]
     just_decode: bool,
 
     /// Just checks the correctness of Cranelift IR translated from Wasm
-    #[clap(short = 'c')]
+    #[arg(short = 'c')]
     check_translation: bool,
 
     /// Use colors in output? [options: auto/never/always; default: auto]
-    #[clap(long = "color", default_value("auto"))]
+    #[arg(long = "color", default_value("auto"))]
     color: ColorOpt,
 }
 
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 8e1709ff1..1192017dc 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -259,6 +259,10 @@ wasmtime_option_group! {
         pub inherit_network: Option<bool>,
         /// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not.
         pub allow_ip_name_lookup: Option<bool>,
+        /// Indicates whether `wasi:sockets` TCP support is enabled or not.
+        pub tcp: Option<bool>,
+        /// Indicates whether `wasi:sockets` UDP support is enabled or not.
+        pub udp: Option<bool>,
         /// Allows imports from the `wasi_unstable` core wasm module.
         pub preview0: Option<bool>,
     }
@@ -286,39 +290,39 @@ pub struct CommonOptions {
     // now.
     /// Optimization and tuning related options for wasm performance, `-O help` to
     /// see all.
-    #[clap(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
     opts_raw: Vec<opt::CommaSeparated<Optimize>>,
 
     /// Codegen-related configuration options, `-C help` to see all.
-    #[clap(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
     codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
 
     /// Debug-related configuration options, `-D help` to see all.
-    #[clap(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
     debug_raw: Vec<opt::CommaSeparated<Debug>>,
 
     /// Options for configuring semantic execution of WebAssembly, `-W help` to see
     /// all.
-    #[clap(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
     wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
 
     /// Options for configuring WASI and its proposals, `-S help` to see all.
-    #[clap(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
+    #[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
     wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
 
     // These fields are filled in by the `configure` method below via the
     // options parsed from the CLI above. This is what the CLI should use.
-    #[clap(skip)]
+    #[arg(skip)]
     configured: bool,
-    #[clap(skip)]
+    #[arg(skip)]
     pub opts: OptimizeOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub codegen: CodegenOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub debug: DebugOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub wasm: WasmOptions,
-    #[clap(skip)]
+    #[arg(skip)]
     pub wasi: WasiOptions,
 }
 
diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs
index 0416b1613..d0f49b547 100644
--- a/crates/environ/examples/factc.rs
+++ b/crates/environ/examples/factc.rs
@@ -24,53 +24,53 @@ use wasmtime_environ::fact::Module;
 #[derive(Parser)]
 struct Factc {
     /// Whether or not debug code is inserted into the generated adapter.
-    #[clap(long)]
+    #[arg(long)]
     debug: bool,
 
     /// Whether or not the lifting options (the callee of the exported adapter)
     /// uses a 64-bit memory as opposed to a 32-bit memory.
-    #[clap(long)]
+    #[arg(long)]
     lift64: bool,
 
     /// Whether or not the lowering options (the caller of the exported adapter)
     /// uses a 64-bit memory as opposed to a 32-bit memory.
-    #[clap(long)]
+    #[arg(long)]
     lower64: bool,
 
     /// Whether or not a call to a `post-return` configured function is enabled
     /// or not.
-    #[clap(long)]
+    #[arg(long)]
     post_return: bool,
 
     /// Whether or not to skip validation of the generated adapter module.
-    #[clap(long)]
+    #[arg(long)]
     skip_validate: bool,
 
     /// Where to place the generated adapter module. Standard output is used if
     /// this is not specified.
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 
     /// Output the text format for WebAssembly instead of the binary format.
-    #[clap(short, long)]
+    #[arg(short, long)]
     text: bool,
 
-    #[clap(long, value_parser = parse_string_encoding, default_value = "utf8")]
+    #[arg(long, value_parser = parse_string_encoding, default_value = "utf8")]
     lift_str: StringEncoding,
 
-    #[clap(long, value_parser = parse_string_encoding, default_value = "utf8")]
+    #[arg(long, value_parser = parse_string_encoding, default_value = "utf8")]
     lower_str: StringEncoding,
 
     /// TODO
     input: PathBuf,
 }
 
-fn parse_string_encoding(name: &str) -> anyhow::Result<StringEncoding> {
+fn parse_string_encoding(name: &str) -> Result<StringEncoding> {
     Ok(match name {
         "utf8" => StringEncoding::Utf8,
         "utf16" => StringEncoding::Utf16,
         "compact-utf16" => StringEncoding::CompactUtf16,
-        other => anyhow::bail!("invalid string encoding: `{other}`"),
+        other => bail!("invalid string encoding: `{other}`"),
     })
 }
 
diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs
index b1a5135bb..11b43ecd4 100644
--- a/crates/wasi-common/src/error.rs
+++ b/crates/wasi-common/src/error.rs
@@ -7,7 +7,7 @@
 //! can be provided with the [`Error::context`] method. This context is only observable with the
 //! `Display` and `Debug` impls of the error.
 
-pub use crate::snapshots::preview_1::error::{Errno, Error, ErrorExt};
+pub use crate::snapshots::preview_1::error::{Error, ErrorExt};
 use std::fmt;
 
 /// An error returned from the `proc_exit` host syscall.
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index fd7600b9b..acda4e7e9 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -11,9 +11,9 @@ pub mod bindings {
     wasmtime::component::bindgen!({
         path: "wit",
         interfaces: "
-            import wasi:http/incoming-handler@0.2.0-rc-2023-11-10;
-            import wasi:http/outgoing-handler@0.2.0-rc-2023-11-10;
-            import wasi:http/types@0.2.0-rc-2023-11-10;
+            import wasi:http/incoming-handler@0.2.0-rc-2023-12-05;
+            import wasi:http/outgoing-handler@0.2.0-rc-2023-12-05;
+            import wasi:http/types@0.2.0-rc-2023-12-05;
         ",
         tracing: true,
         async: false,
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index 1657d9a36..2a9a92b98 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -14,8 +14,8 @@ use std::sync::Arc;
 use std::time::Duration;
 use tokio::net::TcpStream;
 use tokio::time::timeout;
-use wasmtime::component::Resource;
-use wasmtime_wasi::preview2::{self, AbortOnDropJoinHandle, Subscribe, Table};
+use wasmtime::component::{Resource, ResourceTable};
+use wasmtime_wasi::preview2::{self, AbortOnDropJoinHandle, Subscribe};
 
 /// Capture the state necessary for use in the wasi-http API implementation.
 pub struct WasiHttpCtx;
@@ -31,7 +31,7 @@ pub struct OutgoingRequest {
 
 pub trait WasiHttpView: Send {
     fn ctx(&mut self) -> &mut WasiHttpCtx;
-    fn table(&mut self) -> &mut Table;
+    fn table(&mut self) -> &mut ResourceTable;
 
     fn new_incoming_request(
         &mut self,
diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index 74ee18c2f..351f8f59d 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -11,10 +11,10 @@ use crate::{
 use anyhow::Context;
 use std::any::Any;
 use std::str::FromStr;
-use wasmtime::component::Resource;
+use wasmtime::component::{Resource, ResourceTable};
 use wasmtime_wasi::preview2::{
     bindings::io::streams::{InputStream, OutputStream},
-    Pollable, Table,
+    Pollable,
 };
 
 impl<T: WasiHttpView> crate::bindings::http::types::Host for T {
@@ -49,7 +49,7 @@ fn get_content_length(fields: &FieldMap) -> Result<Option<u64>, ()> {
 
 /// Take ownership of the underlying [`FieldMap`] associated with this fields resource. If the
 /// fields resource references another fields, the returned [`FieldMap`] will be cloned.
-fn move_fields(table: &mut Table, id: Resource<HostFields>) -> wasmtime::Result<FieldMap> {
+fn move_fields(table: &mut ResourceTable, id: Resource<HostFields>) -> wasmtime::Result<FieldMap> {
     match table.delete(id)? {
         HostFields::Ref { parent, get_fields } => {
             let entry = table.get_any_mut(parent)?;
@@ -61,7 +61,7 @@ fn move_fields(table: &mut Table, id: Resource<HostFields>) -> wasmtime::Result<
 }
 
 fn get_fields<'a>(
-    table: &'a mut Table,
+    table: &'a mut ResourceTable,
     id: &Resource<HostFields>,
 ) -> wasmtime::Result<&'a FieldMap> {
     let fields = table.get(&id)?;
@@ -80,7 +80,7 @@ fn get_fields<'a>(
 }
 
 fn get_fields_mut<'a>(
-    table: &'a mut Table,
+    table: &'a mut ResourceTable,
     id: &Resource<HostFields>,
 ) -> wasmtime::Result<Result<&'a mut FieldMap, types::HeaderError>> {
     match table.get_mut(&id)? {
diff --git a/crates/wasi-http/wit/command-extended.wit b/crates/wasi-http/wit/command-extended.wit
index 92c7be0db..16f01bc23 100644
--- a/crates/wasi-http/wit/command-extended.wit
+++ b/crates/wasi-http/wit/command-extended.wit
@@ -1,6 +1,6 @@
 // All of the same imports and exports available in the wasi:cli/command world
 // with addition of HTTP proxy related imports:
 world command-extended {
-  include wasi:cli/command@0.2.0-rc-2023-11-10;
-  import wasi:http/outgoing-handler@0.2.0-rc-2023-11-10;
+  include wasi:cli/command@0.2.0-rc-2023-12-05;
+  import wasi:http/outgoing-handler@0.2.0-rc-2023-12-05;
 }
diff --git a/crates/wasi-http/wit/deps/cli/command.wit b/crates/wasi-http/wit/deps/cli/command.wit
index 74811d327..cc82ae5dc 100644
--- a/crates/wasi-http/wit/deps/cli/command.wit
+++ b/crates/wasi-http/wit/deps/cli/command.wit
@@ -1,7 +1,7 @@
-package wasi:cli@0.2.0-rc-2023-11-10;
+package wasi:cli@0.2.0-rc-2023-12-05;
 
 world command {
-  include reactor;
+  include imports;
 
   export run;
 }
diff --git a/crates/wasi-http/wit/deps/cli/imports.wit b/crates/wasi-http/wit/deps/cli/imports.wit
new file mode 100644
index 000000000..9965ea35e
--- /dev/null
+++ b/crates/wasi-http/wit/deps/cli/imports.wit
@@ -0,0 +1,20 @@
+package wasi:cli@0.2.0-rc-2023-12-05;
+
+world imports {
+  include wasi:clocks/imports@0.2.0-rc-2023-11-10;
+  include wasi:filesystem/imports@0.2.0-rc-2023-11-10;
+  include wasi:sockets/imports@0.2.0-rc-2023-11-10;
+  include wasi:random/imports@0.2.0-rc-2023-11-10;
+  include wasi:io/imports@0.2.0-rc-2023-11-10;
+
+  import environment;
+  import exit;
+  import stdin;
+  import stdout;
+  import stderr;
+  import terminal-input;
+  import terminal-output;
+  import terminal-stdin;
+  import terminal-stdout;
+  import terminal-stderr;
+}
diff --git a/crates/wasi-http/wit/deps/cli/reactor.wit b/crates/wasi-http/wit/deps/cli/reactor.wit
deleted file mode 100644
index eafa2fd49..000000000
--- a/crates/wasi-http/wit/deps/cli/reactor.wit
+++ /dev/null
@@ -1,31 +0,0 @@
-package wasi:cli@0.2.0-rc-2023-11-10;
-
-world reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-10;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-10;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-10;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-10;
-  import wasi:sockets/network@0.2.0-rc-2023-11-10;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-10;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-10;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-10;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-10;
-  import wasi:random/random@0.2.0-rc-2023-11-10;
-  import wasi:random/insecure@0.2.0-rc-2023-11-10;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-10;
-  import wasi:io/poll@0.2.0-rc-2023-11-10;
-  import wasi:io/streams@0.2.0-rc-2023-11-10;
-
-  import environment;
-  import exit;
-  import stdin;
-  import stdout;
-  import stderr;
-  import terminal-input;
-  import terminal-output;
-  import terminal-stdin;
-  import terminal-stdout;
-  import terminal-stderr;
-}
diff --git a/crates/wasi-http/wit/deps/http/proxy.wit b/crates/wasi-http/wit/deps/http/proxy.wit
index 453f59051..0f466c93c 100644
--- a/crates/wasi-http/wit/deps/http/proxy.wit
+++ b/crates/wasi-http/wit/deps/http/proxy.wit
@@ -1,4 +1,4 @@
-package wasi:http@0.2.0-rc-2023-11-10;
+package wasi:http@0.2.0-rc-2023-12-05;
 
 /// The `wasi:http/proxy` world captures a widely-implementable intersection of
 /// hosts that includes HTTP forward and reverse proxies. Components targeting
@@ -6,20 +6,19 @@ package wasi:http@0.2.0-rc-2023-11-10;
 /// outgoing HTTP requests.
 world proxy {
   /// HTTP proxies have access to time and randomness.
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
+  include wasi:clocks/imports@0.2.0-rc-2023-11-10;
   import wasi:random/random@0.2.0-rc-2023-11-10;
 
   /// Proxies have standard output and error streams which are expected to
   /// terminate in a developer-facing console provided by the host.
-  import wasi:cli/stdout@0.2.0-rc-2023-11-10;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-10;
+  import wasi:cli/stdout@0.2.0-rc-2023-12-05;
+  import wasi:cli/stderr@0.2.0-rc-2023-12-05;
 
   /// TODO: this is a temporary workaround until component tooling is able to
   /// gracefully handle the absence of stdin. Hosts must return an eof stream
   /// for this import, which is what wasi-libc + tooling will do automatically
   /// when this import is properly removed.
-  import wasi:cli/stdin@0.2.0-rc-2023-11-10;
+  import wasi:cli/stdin@0.2.0-rc-2023-12-05;
 
   /// This is the default handler to use when user code simply wants to make an
   /// HTTP request (e.g., via `fetch()`).
diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs
index 0c9cab019..23e0a45d4 100644
--- a/crates/wasi-preview1-component-adapter/src/descriptors.rs
+++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs
@@ -20,6 +20,8 @@ pub enum Descriptor {
 
     /// Input and/or output wasi-streams, along with stream metadata.
     Streams(Streams),
+
+    Bad,
 }
 
 /// Input and/or output wasi-streams, along with a stream type that
@@ -358,7 +360,7 @@ impl Descriptors {
     ) -> Result<&mut Streams, Errno> {
         match self.get_mut(fd)? {
             Descriptor::Streams(streams) => Ok(streams),
-            Descriptor::Closed(_) => Err(error),
+            Descriptor::Closed(_) | Descriptor::Bad => Err(error),
         }
     }
 
@@ -420,14 +422,14 @@ impl Descriptors {
     pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> {
         match self.get(fd)? {
             Descriptor::Streams(streams) => streams.get_read_stream(),
-            Descriptor::Closed(_) => Err(wasi::ERRNO_BADF),
+            Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),
         }
     }
 
     pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> {
         match self.get(fd)? {
             Descriptor::Streams(streams) => streams.get_write_stream(),
-            Descriptor::Closed(_) => Err(wasi::ERRNO_BADF),
+            Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),
         }
     }
 }
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index 22fcb171c..8ef496d56 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -65,7 +65,7 @@ pub mod bindings {
     #[cfg(feature = "reactor")]
     wit_bindgen::generate!({
         path: "../wasi/wit",
-        world: "wasi:cli/reactor",
+        world: "wasi:cli/imports",
         std_feature,
         raw_strings,
         // Automatically generated bindings for these functions will allocate
@@ -87,9 +87,9 @@ pub mod bindings {
                 import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
                 import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
                 import wasi:random/random@0.2.0-rc-2023-11-10;
-                import wasi:cli/stdout@0.2.0-rc-2023-11-10;
-                import wasi:cli/stderr@0.2.0-rc-2023-11-10;
-                import wasi:cli/stdin@0.2.0-rc-2023-11-10;
+                import wasi:cli/stdout@0.2.0-rc-2023-12-05;
+                import wasi:cli/stderr@0.2.0-rc-2023-12-05;
+                import wasi:cli/stdin@0.2.0-rc-2023-12-05;
             }
         "#,
         std_feature,
@@ -98,7 +98,7 @@ pub mod bindings {
     });
 }
 
-#[export_name = "wasi:cli/run@0.2.0-rc-2023-11-10#run"]
+#[export_name = "wasi:cli/run@0.2.0-rc-2023-12-05#run"]
 #[cfg(feature = "command")]
 pub unsafe extern "C" fn run() -> u32 {
     #[link(wasm_import_module = "__main_module__")]
@@ -145,6 +145,26 @@ impl<T, E> TrappingUnwrap<T> for Result<T, E> {
     }
 }
 
+/// Allocate a file descriptor which will generate an `ERRNO_BADF` if passed to
+/// any WASI Preview 1 function implemented by this adapter.
+///
+/// This is intended for use by `wasi-libc` during its incremental transition
+/// from WASI Preview 1 to Preview 2.  It will use this function to reserve
+/// descriptors for its own use, valid only for use with libc functions.
+#[no_mangle]
+pub unsafe extern "C" fn adapter_open_badfd(fd: *mut u32) -> Errno {
+    State::with(|state| {
+        *fd = state.descriptors_mut().open(Descriptor::Bad)?;
+        Ok(())
+    })
+}
+
+/// Close a descriptor previously opened using `adapter_open_badfd`.
+#[no_mangle]
+pub unsafe extern "C" fn adapter_close_badfd(fd: u32) -> Errno {
+    State::with(|state| state.descriptors_mut().close(fd))
+}
+
 #[no_mangle]
 pub unsafe extern "C" fn reset_adapter_state() {
     let state = get_state_ptr();
@@ -525,6 +545,10 @@ pub unsafe extern "C" fn fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize)
 #[no_mangle]
 pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno {
     State::with(|state| {
+        if let Descriptor::Bad = state.descriptors().get(fd)? {
+            return Err(wasi::ERRNO_BADF);
+        }
+
         // If there's a dirent cache entry for this file descriptor then drop
         // it since the descriptor is being closed and future calls to
         // `fd_readdir` should return an error.
@@ -669,7 +693,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
                     });
                     Ok(())
                 }
-                Descriptor::Closed(_) => Err(ERRNO_BADF),
+                Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
             }
         })
     }
@@ -716,7 +740,7 @@ pub unsafe extern "C" fn fd_fdstat_set_rights(
         let ds = state.descriptors();
         match ds.get(fd)? {
             Descriptor::Streams(..) => Ok(()),
-            Descriptor::Closed(..) => Err(wasi::ERRNO_BADF),
+            Descriptor::Closed(..) | Descriptor::Bad => Err(wasi::ERRNO_BADF),
         }
     })
 }
@@ -1028,7 +1052,7 @@ pub unsafe extern "C" fn fd_read(
                 forget(data);
                 Ok(())
             }
-            Descriptor::Closed(_) => Err(ERRNO_BADF),
+            Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
         }
     })
 }
@@ -1446,7 +1470,7 @@ pub unsafe extern "C" fn fd_write(
                 *nwritten = nbytes;
                 Ok(())
             }
-            Descriptor::Closed(_) => Err(ERRNO_BADF),
+            Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
         }
     })
 }
@@ -2641,7 +2665,7 @@ impl State {
     #[cfg(not(feature = "proxy"))]
     fn get_environment(&self) -> &[StrTuple] {
         if self.env_vars.get().is_none() {
-            #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")]
+            #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-12-05")]
             extern "C" {
                 #[link_name = "get-environment"]
                 fn get_environment_import(rval: *mut StrTupleList);
@@ -2666,7 +2690,7 @@ impl State {
     #[cfg(not(feature = "proxy"))]
     fn get_args(&self) -> &[WasmStr] {
         if self.args.get().is_none() {
-            #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")]
+            #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-12-05")]
             extern "C" {
                 #[link_name = "get-arguments"]
                 fn get_args_import(rval: *mut WasmStrList);
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index 73cd0e116..d3d52408d 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -4,7 +4,7 @@ use crate::preview2::{
     filesystem::Dir,
     pipe, random, stdio,
     stdio::{StdinStream, StdoutStream},
-    DirPerms, FilePerms, Table,
+    DirPerms, FilePerms,
 };
 use cap_rand::{Rng, RngCore, SeedableRng};
 use cap_std::ipnet::{self, IpNet};
@@ -12,6 +12,8 @@ use cap_std::net::Pool;
 use cap_std::{ambient_authority, AmbientAuthority};
 use std::mem;
 use std::net::{Ipv4Addr, Ipv6Addr};
+use std::sync::Arc;
+use wasmtime::component::ResourceTable;
 
 pub struct WasiCtxBuilder {
     stdin: Box<dyn StdinStream>,
@@ -27,7 +29,7 @@ pub struct WasiCtxBuilder {
     insecure_random_seed: u128,
     wall_clock: Box<dyn HostWallClock + Send + Sync>,
     monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
-    allow_ip_name_lookup: bool,
+    allowed_network_uses: AllowedNetworkUses,
     built: bool,
 }
 
@@ -77,7 +79,7 @@ impl WasiCtxBuilder {
             insecure_random_seed,
             wall_clock: wall_clock(),
             monotonic_clock: monotonic_clock(),
-            allow_ip_name_lookup: false,
+            allowed_network_uses: AllowedNetworkUses::default(),
             built: false,
         }
     }
@@ -250,7 +252,19 @@ impl WasiCtxBuilder {
 
     /// Allow usage of `wasi:sockets/ip-name-lookup`
     pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
-        self.allow_ip_name_lookup = enable;
+        self.allowed_network_uses.ip_name_lookup = enable;
+        self
+    }
+
+    /// Allow usage of UDP
+    pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
+        self.allowed_network_uses.udp = enable;
+        self
+    }
+
+    /// Allow usage of TCP
+    pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
+        self.allowed_network_uses.tcp = enable;
         self
     }
 
@@ -278,7 +292,7 @@ impl WasiCtxBuilder {
             insecure_random_seed,
             wall_clock,
             monotonic_clock,
-            allow_ip_name_lookup,
+            allowed_network_uses,
             built: _,
         } = mem::replace(self, Self::new());
         self.built = true;
@@ -290,20 +304,20 @@ impl WasiCtxBuilder {
             env,
             args,
             preopens,
-            pool,
+            pool: Arc::new(pool),
             random,
             insecure_random,
             insecure_random_seed,
             wall_clock,
             monotonic_clock,
-            allow_ip_name_lookup,
+            allowed_network_uses,
         }
     }
 }
 
 pub trait WasiView: Send {
-    fn table(&self) -> &Table;
-    fn table_mut(&mut self) -> &mut Table;
+    fn table(&self) -> &ResourceTable;
+    fn table_mut(&mut self) -> &mut ResourceTable;
     fn ctx(&self) -> &WasiCtx;
     fn ctx_mut(&mut self) -> &mut WasiCtx;
 }
@@ -320,6 +334,48 @@ pub struct WasiCtx {
     pub(crate) stdin: Box<dyn StdinStream>,
     pub(crate) stdout: Box<dyn StdoutStream>,
     pub(crate) stderr: Box<dyn StdoutStream>,
-    pub(crate) pool: Pool,
-    pub(crate) allow_ip_name_lookup: bool,
+    pub(crate) pool: Arc<Pool>,
+    pub(crate) allowed_network_uses: AllowedNetworkUses,
+}
+
+pub struct AllowedNetworkUses {
+    pub ip_name_lookup: bool,
+    pub udp: bool,
+    pub tcp: bool,
+}
+
+impl Default for AllowedNetworkUses {
+    fn default() -> Self {
+        Self {
+            ip_name_lookup: false,
+            udp: true,
+            tcp: true,
+        }
+    }
+}
+
+impl AllowedNetworkUses {
+    pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
+        if !self.udp {
+            return Err(std::io::Error::new(
+                std::io::ErrorKind::PermissionDenied,
+                "UDP is not allowed",
+            )
+            .into());
+        }
+
+        Ok(())
+    }
+
+    pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
+        if !self.tcp {
+            return Err(std::io::Error::new(
+                std::io::ErrorKind::PermissionDenied,
+                "TCP is not allowed",
+            )
+            .into());
+        }
+
+        Ok(())
+    }
 }
diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs
index d8dc2bc68..1f1d149dd 100644
--- a/crates/wasi/src/preview2/filesystem.rs
+++ b/crates/wasi/src/preview2/filesystem.rs
@@ -1,7 +1,6 @@
 use crate::preview2::bindings::filesystem::types;
 use crate::preview2::{
-    spawn_blocking, AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe, TableError,
-    TrappableError,
+    spawn_blocking, AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe, TrappableError,
 };
 use anyhow::anyhow;
 use bytes::{Bytes, BytesMut};
@@ -13,8 +12,8 @@ pub type FsResult<T> = Result<T, FsError>;
 
 pub type FsError = TrappableError<types::ErrorCode>;
 
-impl From<TableError> for FsError {
-    fn from(error: TableError) -> Self {
+impl From<wasmtime::component::ResourceTableError> for FsError {
+    fn from(error: wasmtime::component::ResourceTableError) -> Self {
         Self::trap(error)
     }
 }
diff --git a/crates/wasi/src/preview2/host/clocks.rs b/crates/wasi/src/preview2/host/clocks.rs
index 4467b3681..2f16b9e10 100644
--- a/crates/wasi/src/preview2/host/clocks.rs
+++ b/crates/wasi/src/preview2/host/clocks.rs
@@ -43,7 +43,7 @@ impl<T: WasiView> wall_clock::Host for T {
 }
 
 fn subscribe_to_duration(
-    table: &mut crate::preview2::Table,
+    table: &mut wasmtime::component::ResourceTable,
     duration: tokio::time::Duration,
 ) -> anyhow::Result<Resource<Pollable>> {
     let sleep = if duration.is_zero() {
diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs
index 39559ed43..4a70efee5 100644
--- a/crates/wasi/src/preview2/host/filesystem.rs
+++ b/crates/wasi/src/preview2/host/filesystem.rs
@@ -6,9 +6,9 @@ use crate::preview2::bindings::filesystem::types::{
 use crate::preview2::bindings::io::streams::{InputStream, OutputStream};
 use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator};
 use crate::preview2::filesystem::{FileInputStream, FileOutputStream};
-use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, Table, WasiView};
+use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, WasiView};
 use anyhow::Context;
-use wasmtime::component::Resource;
+use wasmtime::component::{Resource, ResourceTable};
 
 mod sync;
 
@@ -840,7 +840,7 @@ impl<T: WasiView> HostDirectoryEntryStream for T {
 }
 
 async fn get_descriptor_metadata(
-    table: &Table,
+    table: &ResourceTable,
     fd: Resource<types::Descriptor>,
 ) -> FsResult<cap_std::fs::Metadata> {
     match table.get(&fd)? {
@@ -1059,7 +1059,7 @@ mod test {
     use super::*;
     #[test]
     fn table_readdir_works() {
-        let mut table = Table::new();
+        let mut table = ResourceTable::new();
         let ix = table
             .push(ReaddirIterator::new(std::iter::empty()))
             .unwrap();
diff --git a/crates/wasi/src/preview2/host/instance_network.rs b/crates/wasi/src/preview2/host/instance_network.rs
index 6c36a7384..428151dcc 100644
--- a/crates/wasi/src/preview2/host/instance_network.rs
+++ b/crates/wasi/src/preview2/host/instance_network.rs
@@ -7,7 +7,7 @@ impl<T: WasiView> instance_network::Host for T {
     fn instance_network(&mut self) -> Result<Resource<Network>, anyhow::Error> {
         let network = Network {
             pool: self.ctx().pool.clone(),
-            allow_ip_name_lookup: self.ctx().allow_ip_name_lookup,
+            allow_ip_name_lookup: self.ctx().allowed_network_uses.ip_name_lookup,
         };
         let network = self.table_mut().push(network)?;
         Ok(network)
diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs
index 0f977133f..436a92367 100644
--- a/crates/wasi/src/preview2/host/tcp.rs
+++ b/crates/wasi/src/preview2/host/tcp.rs
@@ -28,6 +28,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         network: Resource<Network>,
         local_address: IpSocketAddress,
     ) -> SocketResult<()> {
+        self.ctx().allowed_network_uses.check_allowed_tcp()?;
         let table = self.table_mut();
         let socket = table.get(&this)?;
         let network = table.get(&network)?;
@@ -88,6 +89,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         network: Resource<Network>,
         remote_address: IpSocketAddress,
     ) -> SocketResult<()> {
+        self.ctx().allowed_network_uses.check_allowed_tcp()?;
         let table = self.table_mut();
         let r = {
             let socket = table.get(&this)?;
@@ -186,6 +188,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn start_listen(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
+        self.ctx().allowed_network_uses.check_allowed_tcp()?;
         let table = self.table_mut();
         let socket = table.get_mut(&this)?;
 
@@ -233,6 +236,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         Resource<InputStream>,
         Resource<OutputStream>,
     )> {
+        self.ctx().allowed_network_uses.check_allowed_tcp()?;
         let table = self.table();
         let socket = table.get(&this)?;
 
diff --git a/crates/wasi/src/preview2/host/udp.rs b/crates/wasi/src/preview2/host/udp.rs
index 9c6d20890..cd6dd62c3 100644
--- a/crates/wasi/src/preview2/host/udp.rs
+++ b/crates/wasi/src/preview2/host/udp.rs
@@ -33,21 +33,26 @@ impl<T: WasiView> udp::HostUdpSocket for T {
         network: Resource<Network>,
         local_address: IpSocketAddress,
     ) -> SocketResult<()> {
+        self.ctx().allowed_network_uses.check_allowed_udp()?;
         let table = self.table_mut();
-        let socket = table.get(&this)?;
-        let network = table.get(&network)?;
-        let local_address: SocketAddr = local_address.into();
 
-        match socket.udp_state {
+        match table.get(&this)?.udp_state {
             UdpState::Default => {}
             UdpState::BindStarted => return Err(ErrorCode::ConcurrencyConflict.into()),
             UdpState::Bound | UdpState::Connected => return Err(ErrorCode::InvalidState.into()),
         }
 
+        // Set the pool on the socket so later functions have access to it through the socket handle
+        let pool = table.get(&network)?.pool.clone();
+        table.get_mut(&this)?.pool.replace(pool.clone());
+
+        let socket = table.get(&this)?;
+        let local_address: SocketAddr = local_address.into();
+
         util::validate_address_family(&local_address, &socket.family)?;
 
         {
-            let binder = network.pool.udp_binder(local_address)?;
+            let binder = pool.udp_binder(local_address)?;
             let udp_socket = &*socket
                 .udp_socket()
                 .as_socketlike_view::<cap_std::net::UdpSocket>();
@@ -127,8 +132,13 @@ impl<T: WasiView> udp::HostUdpSocket for T {
 
         // Step #2: (Re)connect
         if let Some(connect_addr) = remote_address {
+            let Some(pool) = socket.pool.as_ref() else {
+                return Err(ErrorCode::InvalidState.into());
+            };
             util::validate_remote_address(&connect_addr)?;
             util::validate_address_family(&connect_addr, &socket.family)?;
+            // We don't actually use the connecter, we just use it to verify that `connect_addr` is allowed
+            let _ = pool.udp_connecter(connect_addr)?;
 
             rustix::net::connect(socket.udp_socket(), &connect_addr).map_err(
                 |error| match error {
@@ -152,6 +162,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
             remote_address,
             family: socket.family,
             send_state: SendState::Idle,
+            pool: socket.pool.clone(),
         };
 
         Ok((
@@ -344,7 +355,6 @@ impl<T: WasiView> udp::HostIncomingDatagramStream for T {
                 _ => {}
             }
 
-            // FIXME: check permission to receive from `received_addr`.
             Ok(Some(udp::IncomingDatagram {
                 data: buf[..size].into(),
                 remote_address: received_addr.into(),
@@ -447,7 +457,16 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
 
             let provided_addr = datagram.remote_address.map(SocketAddr::from);
             let addr = match (stream.remote_address, provided_addr) {
-                (None, Some(addr)) => addr,
+                (None, Some(addr)) => {
+                    let Some(pool) = stream.pool.as_ref() else {
+                        return Err(ErrorCode::InvalidState.into());
+                    };
+                    // We don't actually use the connecter, we just use it to verify that `addr`
+                    // is allowed. We only need to check the provided addr as the stream's remote
+                    // address was checked when the stream was created.
+                    let _ = pool.udp_connecter(addr)?;
+                    addr
+                }
                 (Some(addr), None) => addr,
                 (Some(connected_addr), Some(provided_addr)) if connected_addr == provided_addr => {
                     connected_addr
@@ -458,7 +477,6 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
             util::validate_remote_address(&addr)?;
             util::validate_address_family(&addr, &stream.family)?;
 
-            // FIXME: check permission to send to `addr`.
             if stream.remote_address == Some(addr) {
                 stream.inner.try_send(&datagram.data)?;
             } else {
diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs
index 481b588e7..f7fc9004e 100644
--- a/crates/wasi/src/preview2/mod.rs
+++ b/crates/wasi/src/preview2/mod.rs
@@ -36,7 +36,6 @@ pub mod preview1;
 mod random;
 mod stdio;
 mod stream;
-mod table;
 mod tcp;
 mod udp;
 mod write_stream;
@@ -54,9 +53,9 @@ pub use self::stdio::{
 pub use self::stream::{
     HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, StreamResult,
 };
-pub use self::table::{Table, TableError};
 pub use cap_fs_ext::SystemTimeSpec;
 pub use cap_rand::RngCore;
+pub use wasmtime::component::{ResourceTable, ResourceTableError};
 
 pub mod bindings {
     // Generate traits for synchronous bindings.
@@ -95,7 +94,7 @@ pub mod bindings {
 
     wasmtime::component::bindgen!({
         path: "wit",
-        world: "wasi:cli/reactor",
+        world: "wasi:cli/imports",
         tracing: true,
         async: {
             // Only these functions are `async` and everything else is sync
diff --git a/crates/wasi/src/preview2/network.rs b/crates/wasi/src/preview2/network.rs
index 65c380bd1..6384b1803 100644
--- a/crates/wasi/src/preview2/network.rs
+++ b/crates/wasi/src/preview2/network.rs
@@ -1,10 +1,11 @@
 use crate::preview2::bindings::sockets::network::{Ipv4Address, Ipv6Address};
 use crate::preview2::bindings::wasi::sockets::network::ErrorCode;
-use crate::preview2::{TableError, TrappableError};
+use crate::preview2::TrappableError;
 use cap_std::net::Pool;
+use std::sync::Arc;
 
 pub struct Network {
-    pub pool: Pool,
+    pub pool: Arc<Pool>,
     pub allow_ip_name_lookup: bool,
 }
 
@@ -12,8 +13,8 @@ pub type SocketResult<T> = Result<T, SocketError>;
 
 pub type SocketError = TrappableError<ErrorCode>;
 
-impl From<TableError> for SocketError {
-    fn from(error: TableError) -> Self {
+impl From<wasmtime::component::ResourceTableError> for SocketError {
+    fn from(error: wasmtime::component::ResourceTableError) -> Self {
         Self::trap(error)
     }
 }
diff --git a/crates/wasi/src/preview2/poll.rs b/crates/wasi/src/preview2/poll.rs
index 8e2b5385f..4fba2a994 100644
--- a/crates/wasi/src/preview2/poll.rs
+++ b/crates/wasi/src/preview2/poll.rs
@@ -1,11 +1,11 @@
-use crate::preview2::{bindings::io::poll, Table, WasiView};
+use crate::preview2::{bindings::io::poll, WasiView};
 use anyhow::Result;
 use std::any::Any;
 use std::collections::HashMap;
 use std::future::Future;
 use std::pin::Pin;
 use std::task::{Context, Poll};
-use wasmtime::component::Resource;
+use wasmtime::component::{Resource, ResourceTable};
 
 pub type PollableFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
 pub type MakeFuture = for<'a> fn(&'a mut dyn Any) -> PollableFuture<'a>;
@@ -20,7 +20,7 @@ pub type ClosureFuture = Box<dyn Fn() -> PollableFuture<'static> + Send + Sync +
 pub struct Pollable {
     index: u32,
     make_future: MakeFuture,
-    remove_index_on_delete: Option<fn(&mut Table, u32) -> Result<()>>,
+    remove_index_on_delete: Option<fn(&mut ResourceTable, u32) -> Result<()>>,
 }
 
 #[async_trait::async_trait]
@@ -35,7 +35,7 @@ pub trait Subscribe: Send + Sync + 'static {
 /// resource is deleted. Otherwise the returned resource is considered a "child"
 /// of the given `resource` which means that the given resource cannot be
 /// deleted while the `pollable` is still alive.
-pub fn subscribe<T>(table: &mut Table, resource: Resource<T>) -> Result<Resource<Pollable>>
+pub fn subscribe<T>(table: &mut ResourceTable, resource: Resource<T>) -> Result<Resource<Pollable>>
 where
     T: Subscribe,
 {
diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs
index 967bceb07..8a0c64bd7 100644
--- a/crates/wasi/src/preview2/preview1.rs
+++ b/crates/wasi/src/preview2/preview1.rs
@@ -8,7 +8,7 @@ use crate::preview2::bindings::{
     filesystem::{preopens, types as filesystem},
     io::{poll, streams},
 };
-use crate::preview2::{FsError, IsATTY, StreamError, StreamResult, TableError, WasiView};
+use crate::preview2::{FsError, IsATTY, StreamError, StreamResult, WasiView};
 use anyhow::{bail, Context};
 use std::borrow::Borrow;
 use std::collections::{BTreeMap, HashSet};
@@ -325,7 +325,7 @@ pub trait WasiPreview1View: WasiView {
 // `&mut self` receivers and so this struct lets us extend the lifetime of the `&mut self` borrow
 // of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`]
 // without having to rely on something like `Arc<Mutex<Descriptors>>`, while also being able to
-// call methods like [`TableFsExt::is_file`] and hiding complexity from preview1 method implementations.
+// call methods like [`Descriptor::is_file`] and hiding complexity from preview1 method implementations.
 struct Transaction<'a, T: WasiPreview1View + ?Sized> {
     view: &'a mut T,
     descriptors: Descriptors,
@@ -772,8 +772,8 @@ impl From<filesystem::ErrorCode> for types::Error {
     }
 }
 
-impl From<TableError> for types::Error {
-    fn from(err: TableError) -> Self {
+impl From<wasmtime::component::ResourceTableError> for types::Error {
+    fn from(err: wasmtime::component::ResourceTableError) -> Self {
         types::Error::trap(err.into())
     }
 }
diff --git a/crates/wasi/src/preview2/stream.rs b/crates/wasi/src/preview2/stream.rs
index ee13b6afa..bf2ce502b 100644
--- a/crates/wasi/src/preview2/stream.rs
+++ b/crates/wasi/src/preview2/stream.rs
@@ -1,6 +1,5 @@
 use crate::preview2::filesystem::FileInputStream;
 use crate::preview2::poll::Subscribe;
-use crate::preview2::TableError;
 use anyhow::Result;
 use bytes::Bytes;
 
@@ -73,8 +72,8 @@ impl std::error::Error for StreamError {
     }
 }
 
-impl From<TableError> for StreamError {
-    fn from(error: TableError) -> Self {
+impl From<wasmtime::component::ResourceTableError> for StreamError {
+    fn from(error: wasmtime::component::ResourceTableError) -> Self {
         Self::Trap(error.into())
     }
 }
diff --git a/crates/wasi/src/preview2/udp.rs b/crates/wasi/src/preview2/udp.rs
index 16335d601..13194c1f5 100644
--- a/crates/wasi/src/preview2/udp.rs
+++ b/crates/wasi/src/preview2/udp.rs
@@ -2,6 +2,7 @@ use crate::preview2::poll::Subscribe;
 use crate::preview2::with_ambient_tokio_runtime;
 use async_trait::async_trait;
 use cap_net_ext::{AddressFamily, Blocking, UdpSocketExt};
+use cap_std::net::Pool;
 use io_lifetimes::raw::{FromRawSocketlike, IntoRawSocketlike};
 use std::io;
 use std::net::SocketAddr;
@@ -42,6 +43,9 @@ pub struct UdpSocket {
 
     /// Socket address family.
     pub(crate) family: SocketAddressFamily,
+
+    /// The pool of allowed addresses
+    pub(crate) pool: Option<Arc<Pool>>,
 }
 
 #[async_trait]
@@ -67,6 +71,7 @@ impl UdpSocket {
             inner: Arc::new(socket),
             udp_state: UdpState::Default,
             family: socket_address_family,
+            pool: None,
         })
     }
 
@@ -104,6 +109,9 @@ pub struct OutgoingDatagramStream {
     pub(crate) family: SocketAddressFamily,
 
     pub(crate) send_state: SendState,
+
+    /// The pool of allowed addresses
+    pub(crate) pool: Option<Arc<Pool>>,
 }
 
 pub(crate) enum SendState {
diff --git a/crates/wasi/wit/command-extended.wit b/crates/wasi/wit/command-extended.wit
index 92c7be0db..16f01bc23 100644
--- a/crates/wasi/wit/command-extended.wit
+++ b/crates/wasi/wit/command-extended.wit
@@ -1,6 +1,6 @@
 // All of the same imports and exports available in the wasi:cli/command world
 // with addition of HTTP proxy related imports:
 world command-extended {
-  include wasi:cli/command@0.2.0-rc-2023-11-10;
-  import wasi:http/outgoing-handler@0.2.0-rc-2023-11-10;
+  include wasi:cli/command@0.2.0-rc-2023-12-05;
+  import wasi:http/outgoing-handler@0.2.0-rc-2023-12-05;
 }
diff --git a/crates/wasi/wit/deps/cli/command.wit b/crates/wasi/wit/deps/cli/command.wit
index 74811d327..cc82ae5dc 100644
--- a/crates/wasi/wit/deps/cli/command.wit
+++ b/crates/wasi/wit/deps/cli/command.wit
@@ -1,7 +1,7 @@
-package wasi:cli@0.2.0-rc-2023-11-10;
+package wasi:cli@0.2.0-rc-2023-12-05;
 
 world command {
-  include reactor;
+  include imports;
 
   export run;
 }
diff --git a/crates/wasi/wit/deps/cli/imports.wit b/crates/wasi/wit/deps/cli/imports.wit
new file mode 100644
index 000000000..9965ea35e
--- /dev/null
+++ b/crates/wasi/wit/deps/cli/imports.wit
@@ -0,0 +1,20 @@
+package wasi:cli@0.2.0-rc-2023-12-05;
+
+world imports {
+  include wasi:clocks/imports@0.2.0-rc-2023-11-10;
+  include wasi:filesystem/imports@0.2.0-rc-2023-11-10;
+  include wasi:sockets/imports@0.2.0-rc-2023-11-10;
+  include wasi:random/imports@0.2.0-rc-2023-11-10;
+  include wasi:io/imports@0.2.0-rc-2023-11-10;
+
+  import environment;
+  import exit;
+  import stdin;
+  import stdout;
+  import stderr;
+  import terminal-input;
+  import terminal-output;
+  import terminal-stdin;
+  import terminal-stdout;
+  import terminal-stderr;
+}
diff --git a/crates/wasi/wit/deps/cli/reactor.wit b/crates/wasi/wit/deps/cli/reactor.wit
deleted file mode 100644
index eafa2fd49..000000000
--- a/crates/wasi/wit/deps/cli/reactor.wit
+++ /dev/null
@@ -1,31 +0,0 @@
-package wasi:cli@0.2.0-rc-2023-11-10;
-
-world reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-10;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-10;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-10;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-10;
-  import wasi:sockets/network@0.2.0-rc-2023-11-10;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-10;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-10;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-10;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-10;
-  import wasi:random/random@0.2.0-rc-2023-11-10;
-  import wasi:random/insecure@0.2.0-rc-2023-11-10;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-10;
-  import wasi:io/poll@0.2.0-rc-2023-11-10;
-  import wasi:io/streams@0.2.0-rc-2023-11-10;
-
-  import environment;
-  import exit;
-  import stdin;
-  import stdout;
-  import stderr;
-  import terminal-input;
-  import terminal-output;
-  import terminal-stdin;
-  import terminal-stdout;
-  import terminal-stderr;
-}
diff --git a/crates/wasi/wit/deps/http/proxy.wit b/crates/wasi/wit/deps/http/proxy.wit
index 453f59051..0f466c93c 100644
--- a/crates/wasi/wit/deps/http/proxy.wit
+++ b/crates/wasi/wit/deps/http/proxy.wit
@@ -1,4 +1,4 @@
-package wasi:http@0.2.0-rc-2023-11-10;
+package wasi:http@0.2.0-rc-2023-12-05;
 
 /// The `wasi:http/proxy` world captures a widely-implementable intersection of
 /// hosts that includes HTTP forward and reverse proxies. Components targeting
@@ -6,20 +6,19 @@ package wasi:http@0.2.0-rc-2023-11-10;
 /// outgoing HTTP requests.
 world proxy {
   /// HTTP proxies have access to time and randomness.
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
+  include wasi:clocks/imports@0.2.0-rc-2023-11-10;
   import wasi:random/random@0.2.0-rc-2023-11-10;
 
   /// Proxies have standard output and error streams which are expected to
   /// terminate in a developer-facing console provided by the host.
-  import wasi:cli/stdout@0.2.0-rc-2023-11-10;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-10;
+  import wasi:cli/stdout@0.2.0-rc-2023-12-05;
+  import wasi:cli/stderr@0.2.0-rc-2023-12-05;
 
   /// TODO: this is a temporary workaround until component tooling is able to
   /// gracefully handle the absence of stdin. Hosts must return an eof stream
   /// for this import, which is what wasi-libc + tooling will do automatically
   /// when this import is properly removed.
-  import wasi:cli/stdin@0.2.0-rc-2023-11-10;
+  import wasi:cli/stdin@0.2.0-rc-2023-12-05;
 
   /// This is the default handler to use when user code simply wants to make an
   /// HTTP request (e.g., via `fetch()`).
diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs
index 280b3fdef..6b6a90c42 100644
--- a/crates/wasmtime/src/component/component.rs
+++ b/crates/wasmtime/src/component/component.rs
@@ -127,14 +127,54 @@ impl Component {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Component> {
+        use crate::module::HashedEngineCompileEnv;
+
         engine
             .check_compatible_with_native_host()
             .context("compilation settings are not compatible with the native host")?;
 
-        let (mmap, artifacts) = Component::build_artifacts(engine, binary)?;
-        let mut code_memory = CodeMemory::new(mmap)?;
-        code_memory.publish()?;
-        Component::from_parts(engine, Arc::new(code_memory), Some(artifacts))
+        cfg_if::cfg_if! {
+            if #[cfg(feature = "cache")] {
+                let state = (HashedEngineCompileEnv(engine), binary);
+                let (code, artifacts) = wasmtime_cache::ModuleCacheEntry::new(
+                    "wasmtime",
+                    engine.cache_config(),
+                )
+                .get_data_raw(
+                    &state,
+
+                    // Cache miss, compute the actual artifacts
+                    |(engine, wasm)| -> Result<_> {
+                        let (mmap, artifacts) = Component::build_artifacts(engine.0, wasm)?;
+                        let code = publish_mmap(mmap)?;
+                        Ok((code, Some(artifacts)))
+                    },
+
+                    // Implementation of how to serialize artifacts
+                    |(_engine, _wasm), (code, _info_and_types)| {
+                        Some(code.mmap().to_vec())
+                    },
+
+                    // Cache hit, deserialize the provided artifacts
+                    |(engine, _wasm), serialized_bytes| {
+                        let code = engine.0.load_code_bytes(&serialized_bytes, ObjectKind::Component).ok()?;
+                        Some((code, None))
+                    },
+                )?;
+            } else {
+                let (mmap, artifacts) = Component::build_artifacts(engine, binary)?;
+                let artifacts = Some(artifacts);
+                let code = publish_mmap(mmap)?;
+            }
+        };
+
+        return Component::from_parts(engine, code, artifacts);
+
+        fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
+            let mut code = CodeMemory::new(mmap)?;
+            code.publish()?;
+            Ok(Arc::new(code))
+        }
     }
 
     /// Same as [`Module::deserialize`], but for components.
diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs
index e2028a2be..364942b53 100644
--- a/crates/wasmtime/src/component/mod.rs
+++ b/crates/wasmtime/src/component/mod.rs
@@ -10,6 +10,7 @@ mod func;
 mod instance;
 mod linker;
 mod matching;
+mod resource_table;
 mod resources;
 mod storage;
 mod store;
@@ -21,6 +22,7 @@ pub use self::func::{
 };
 pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
 pub use self::linker::{Linker, LinkerInstance};
+pub use self::resource_table::{ResourceTable, ResourceTableError};
 pub use self::resources::{Resource, ResourceAny};
 pub use self::types::{ResourceType, Type};
 pub use self::values::{Enum, Flags, List, OptionVal, Record, ResultVal, Tuple, Val, Variant};
diff --git a/crates/wasi/src/preview2/table.rs b/crates/wasmtime/src/component/resource_table.rs
similarity index 77%
rename from crates/wasi/src/preview2/table.rs
rename to crates/wasmtime/src/component/resource_table.rs
index 9e60cd88d..fe5244d36 100644
--- a/crates/wasi/src/preview2/table.rs
+++ b/crates/wasmtime/src/component/resource_table.rs
@@ -1,28 +1,36 @@
+use super::Resource;
 use std::any::Any;
 use std::collections::{BTreeSet, HashMap};
-use wasmtime::component::Resource;
 
-#[derive(thiserror::Error, Debug)]
-pub enum TableError {
-    #[error("table has no free keys")]
+#[derive(Debug)]
+/// Errors returned by operations on `ResourceTable`
+pub enum ResourceTableError {
+    /// ResourceTable has no free keys
     Full,
-    #[error("value not present")]
+    /// Resource not present in table
     NotPresent,
-    #[error("value is of another type")]
+    /// Resource present in table, but with a different type
     WrongType,
-    #[error("entry still has children")]
+    /// Resource cannot be deleted because child resources exist in the table. Consult wit docs for
+    /// the particular resource to see which methods may return child resources.
     HasChildren,
 }
 
-/// The `Table` type is designed to map u32 handles to resources. The table is now part of the
-/// public interface to a `WasiCtx` - it is reference counted so that it can be shared beyond a
-/// `WasiCtx` with other WASI proposals (e.g. `wasi-crypto` and `wasi-nn`) to manage their
-/// resources. Elements in the `Table` are `Any` typed.
-///
-/// The `Table` type is intended to model how the Interface Types concept of Resources is shaping
-/// up. Right now it is just an approximation.
+impl std::fmt::Display for ResourceTableError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Full => write!(f, "resource table has no free keys"),
+            Self::NotPresent => write!(f, "resource not present"),
+            Self::WrongType => write!(f, "resource is of another type"),
+            Self::HasChildren => write!(f, "resource has children"),
+        }
+    }
+}
+impl std::error::Error for ResourceTableError {}
+
+/// The `ResourceTable` type maps a `Resource<T>` to its `T`.
 #[derive(Debug)]
-pub struct Table {
+pub struct ResourceTable {
     entries: Vec<Entry>,
     free_head: Option<usize>,
 }
@@ -86,10 +94,10 @@ impl TableEntry {
     }
 }
 
-impl Table {
+impl ResourceTable {
     /// Create an empty table
     pub fn new() -> Self {
-        Table {
+        ResourceTable {
             entries: Vec::new(),
             free_head: None,
         }
@@ -97,7 +105,7 @@ impl Table {
 
     /// Create an empty table with at least the specified capacity.
     pub fn with_capacity(capacity: usize) -> Self {
-        Table {
+        ResourceTable {
             entries: Vec::with_capacity(capacity),
             free_head: None,
         }
@@ -105,7 +113,7 @@ impl Table {
 
     /// Inserts a new value `T` into this table, returning a corresponding
     /// `Resource<T>` which can be used to refer to it after it was inserted.
-    pub fn push<T>(&mut self, entry: T) -> Result<Resource<T>, TableError>
+    pub fn push<T>(&mut self, entry: T) -> Result<Resource<T>, ResourceTableError>
     where
         T: Send + Sync + 'static,
     {
@@ -146,7 +154,7 @@ impl Table {
 
     /// Push a new entry into the table, returning its handle. This will prefer to use free entries
     /// if they exist, falling back on pushing new entries onto the end of the table.
-    fn push_(&mut self, e: TableEntry) -> Result<u32, TableError> {
+    fn push_(&mut self, e: TableEntry) -> Result<u32, ResourceTableError> {
         if let Some(free) = self.pop_free_list() {
             self.entries[free] = Entry::Occupied { entry: e };
             Ok(free as u32)
@@ -155,32 +163,33 @@ impl Table {
                 .entries
                 .len()
                 .try_into()
-                .map_err(|_| TableError::Full)?;
+                .map_err(|_| ResourceTableError::Full)?;
             self.entries.push(Entry::Occupied { entry: e });
             Ok(ix)
         }
     }
 
-    fn occupied(&self, key: u32) -> Result<&TableEntry, TableError> {
+    fn occupied(&self, key: u32) -> Result<&TableEntry, ResourceTableError> {
         self.entries
             .get(key as usize)
             .and_then(Entry::occupied)
-            .ok_or(TableError::NotPresent)
+            .ok_or(ResourceTableError::NotPresent)
     }
 
-    fn occupied_mut(&mut self, key: u32) -> Result<&mut TableEntry, TableError> {
+    fn occupied_mut(&mut self, key: u32) -> Result<&mut TableEntry, ResourceTableError> {
         self.entries
             .get_mut(key as usize)
             .and_then(Entry::occupied_mut)
-            .ok_or(TableError::NotPresent)
+            .ok_or(ResourceTableError::NotPresent)
     }
 
     /// Insert a resource at the next available index, and track that it has a
     /// parent resource.
     ///
     /// The parent must exist to create a child. All children resources must
-    /// be destroyed before a parent can be destroyed - otherwise [`Table::delete`]
-    /// will fail with [`TableError::HasChildren`].
+    /// be destroyed before a parent can be destroyed - otherwise
+    /// [`ResourceTable::delete`] will fail with
+    /// [`ResourceTableError::HasChildren`].
     ///
     /// Parent-child relationships are tracked inside the table to ensure that
     /// a parent resource is not deleted while it has live children. This
@@ -191,13 +200,14 @@ impl Table {
     /// possibility for deadlocks.
     ///
     /// Parent-child relationships may not be modified once created. There
-    /// is no way to observe these relationships through the [`Table`] methods
-    /// except for erroring on deletion, or the [`std::fmt::Debug`] impl.
+    /// is no way to observe these relationships through the [`ResourceTable`]
+    /// methods except for erroring on deletion, or the [`std::fmt::Debug`]
+    /// impl.
     pub fn push_child<T, U>(
         &mut self,
         entry: T,
         parent: &Resource<U>,
-    ) -> Result<Resource<T>, TableError>
+    ) -> Result<Resource<T>, ResourceTableError>
     where
         T: Send + Sync + 'static,
         U: 'static,
@@ -213,33 +223,36 @@ impl Table {
     /// index.
     ///
     /// Multiple shared references can be borrowed at any given time.
-    pub fn get<T: Any + Sized>(&self, key: &Resource<T>) -> Result<&T, TableError> {
+    pub fn get<T: Any + Sized>(&self, key: &Resource<T>) -> Result<&T, ResourceTableError> {
         self.get_(key.rep())?
             .downcast_ref()
-            .ok_or(TableError::WrongType)
+            .ok_or(ResourceTableError::WrongType)
     }
 
-    fn get_(&self, key: u32) -> Result<&dyn Any, TableError> {
+    fn get_(&self, key: u32) -> Result<&dyn Any, ResourceTableError> {
         let r = self.occupied(key)?;
         Ok(&*r.entry)
     }
 
     /// Get an mutable reference to a resource of a given type at a given
     /// index.
-    pub fn get_mut<T: Any + Sized>(&mut self, key: &Resource<T>) -> Result<&mut T, TableError> {
+    pub fn get_mut<T: Any + Sized>(
+        &mut self,
+        key: &Resource<T>,
+    ) -> Result<&mut T, ResourceTableError> {
         self.get_any_mut(key.rep())?
             .downcast_mut()
-            .ok_or(TableError::WrongType)
+            .ok_or(ResourceTableError::WrongType)
     }
 
     /// Returns the raw `Any` at the `key` index provided.
-    pub fn get_any_mut(&mut self, key: u32) -> Result<&mut dyn Any, TableError> {
+    pub fn get_any_mut(&mut self, key: u32) -> Result<&mut dyn Any, ResourceTableError> {
         let r = self.occupied_mut(key)?;
         Ok(&mut *r.entry)
     }
 
     /// Same as `delete`, but typed
-    pub fn delete<T>(&mut self, resource: Resource<T>) -> Result<T, TableError>
+    pub fn delete<T>(&mut self, resource: Resource<T>) -> Result<T, ResourceTableError>
     where
         T: Any,
     {
@@ -247,13 +260,13 @@ impl Table {
         let entry = self.delete_entry(resource.rep())?;
         match entry.entry.downcast() {
             Ok(t) => Ok(*t),
-            Err(_e) => Err(TableError::WrongType),
+            Err(_e) => Err(ResourceTableError::WrongType),
         }
     }
 
-    fn delete_entry(&mut self, key: u32) -> Result<TableEntry, TableError> {
+    fn delete_entry(&mut self, key: u32) -> Result<TableEntry, ResourceTableError> {
         if !self.occupied(key)?.children.is_empty() {
-            return Err(TableError::HasChildren);
+            return Err(ResourceTableError::HasChildren);
         }
         let e = self.free_entry(key as usize);
         if let Some(parent) = e.parent {
@@ -269,11 +282,11 @@ impl Table {
 
     /// Zip the values of the map with mutable references to table entries corresponding to each
     /// key. As the keys in the [HashMap] are unique, this iterator can give mutable references
-    /// with the same lifetime as the mutable reference to the [Table].
+    /// with the same lifetime as the mutable reference to the [ResourceTable].
     pub fn iter_entries<'a, T>(
         &'a mut self,
         map: HashMap<u32, T>,
-    ) -> impl Iterator<Item = (Result<&'a mut dyn Any, TableError>, T)> {
+    ) -> impl Iterator<Item = (Result<&'a mut dyn Any, ResourceTableError>, T)> {
         map.into_iter().map(move |(k, v)| {
             let item = self
                 .occupied_mut(k)
@@ -288,7 +301,7 @@ impl Table {
     pub fn iter_children<T>(
         &self,
         parent: &Resource<T>,
-    ) -> Result<impl Iterator<Item = &(dyn Any + Send + Sync)>, TableError>
+    ) -> Result<impl Iterator<Item = &(dyn Any + Send + Sync)>, ResourceTableError>
     where
         T: 'static,
     {
@@ -300,15 +313,15 @@ impl Table {
     }
 }
 
-impl Default for Table {
+impl Default for ResourceTable {
     fn default() -> Self {
-        Table::new()
+        ResourceTable::new()
     }
 }
 
 #[test]
 pub fn test_free_list() {
-    let mut table = Table::new();
+    let mut table = ResourceTable::new();
 
     let x = table.push(()).unwrap();
     assert_eq!(x.rep(), 0);
diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs
index f1188faad..e88eb23e7 100644
--- a/crates/wasmtime/src/func/typed.rs
+++ b/crates/wasmtime/src/func/typed.rs
@@ -239,7 +239,7 @@ pub unsafe trait WasmTy: Send {
 }
 
 macro_rules! integers {
-    ($($primitive:ident/$get_primitive:ident => $ty:ident in $raw:ident)*) => ($(
+    ($($primitive:ident/$get_primitive:ident => $ty:ident)*) => ($(
         unsafe impl WasmTy for $primitive {
             type Abi = $primitive;
             #[inline]
@@ -275,10 +275,10 @@ macro_rules! integers {
 }
 
 integers! {
-    i32/get_i32 => I32 in i32
-    i64/get_i64 => I64 in i64
-    u32/get_u32 => I32 in i32
-    u64/get_u64 => I64 in i64
+    i32/get_i32 => I32
+    i64/get_i64 => I64
+    u32/get_u32 => I32
+    u64/get_u64 => I64
 }
 
 macro_rules! floats {
diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs
index eca7fd391..2c1422f59 100644
--- a/crates/wit-bindgen/src/lib.rs
+++ b/crates/wit-bindgen/src/lib.rs
@@ -474,7 +474,7 @@ impl Wasmtime {
                         format!(
                             "{}_{}_{snake}",
                             pkgname.namespace.to_snake_case(),
-                            pkgname.name.to_snake_case()
+                            self.name_package_module(resolve, iface.package.unwrap())
                         ),
                     ),
                     None => (format!("exports::{snake}::{camel}"), snake.clone()),
diff --git a/examples/threads.c b/examples/threads.c
index 9329746dc..7e13b9f3a 100644
--- a/examples/threads.c
+++ b/examples/threads.c
@@ -178,7 +178,14 @@ int main(int argc, const char *argv[]) {
     args->engine = engine;
     args->module = shared;
     printf("Initializing thread %d...\n", i);
-    pthread_create(&threads[i], NULL, &run, args);
+
+    // Guarantee at least 2MB of stack to allow running Cranelift in debug mode
+    // on CI.
+    pthread_attr_t attrs;
+    pthread_attr_init(&attrs);
+    pthread_attr_setstacksize(&attrs, 2 << 20);
+    pthread_create(&threads[i], &attrs, &run, args);
+    pthread_attr_destroy(&attrs);
   }
 
   for (int i = 0; i < N_THREADS; i++) {
diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs
index 8324ce329..6abcbf819 100644
--- a/src/bin/wasmtime.rs
+++ b/src/bin/wasmtime.rs
@@ -8,7 +8,7 @@ use clap::Parser;
 
 /// Wasmtime WebAssembly Runtime
 #[derive(Parser, PartialEq)]
-#[clap(
+#[command(
     version = version(),
     after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\
                   \n\
@@ -34,9 +34,9 @@ use clap::Parser;
     args_conflicts_with_subcommands = true
 )]
 struct Wasmtime {
-    #[clap(subcommand)]
+    #[command(subcommand)]
     subcommand: Option<Subcommand>,
-    #[clap(flatten)]
+    #[command(flatten)]
     run: wasmtime_cli::commands::RunCommand,
 }
 
diff --git a/src/commands/compile.rs b/src/commands/compile.rs
index d5f7ef0d3..4a399238f 100644
--- a/src/commands/compile.rs
+++ b/src/commands/compile.rs
@@ -30,30 +30,29 @@ static AFTER_HELP: Lazy<String> = Lazy::new(|| {
 
 /// Compiles a WebAssembly module.
 #[derive(Parser, PartialEq)]
-#[structopt(
-    name = "compile",
+#[command(
     version,
     after_help = AFTER_HELP.as_str()
 )]
 pub struct CompileCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     #[allow(missing_docs)]
     pub common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     pub target: Option<String>,
 
     /// The path of the output compiled module; defaults to <MODULE>.cwasm
-    #[clap(short = 'o', long, value_name = "OUTPUT")]
+    #[arg(short = 'o', long, value_name = "OUTPUT")]
     pub output: Option<PathBuf>,
 
     /// The directory path to write clif files into, one clif file per wasm function.
-    #[clap(long = "emit-clif", value_name = "PATH")]
+    #[arg(long = "emit-clif", value_name = "PATH")]
     pub emit_clif: Option<PathBuf>,
 
     /// The path of the WebAssembly to compile
-    #[clap(index = 1, value_name = "MODULE")]
+    #[arg(index = 1, value_name = "MODULE")]
     pub module: PathBuf,
 }
 
diff --git a/src/commands/config.rs b/src/commands/config.rs
index b18e90ea8..d53b8f9a1 100644
--- a/src/commands/config.rs
+++ b/src/commands/config.rs
@@ -1,23 +1,22 @@
 //! The module that implements the `wasmtime config` command.
 
 use anyhow::Result;
-use clap::Parser;
+use clap::{Parser, Subcommand};
 
 const CONFIG_NEW_AFTER_HELP: &str =
     "If no file path is specified, the system configuration file path will be used.";
 
 /// Controls Wasmtime configuration settings
 #[derive(Parser, PartialEq)]
-#[clap(name = "config")]
 pub struct ConfigCommand {
-    #[clap(subcommand)]
+    #[command(subcommand)]
     subcommand: ConfigSubcommand,
 }
 
-#[derive(clap::Subcommand, PartialEq)]
+#[derive(Subcommand, PartialEq)]
 enum ConfigSubcommand {
     /// Creates a new Wasmtime configuration file
-    #[clap(after_help = CONFIG_NEW_AFTER_HELP)]
+    #[command(after_help = CONFIG_NEW_AFTER_HELP)]
     New(ConfigNewCommand),
 }
 
@@ -32,10 +31,9 @@ impl ConfigCommand {
 
 /// Creates a new Wasmtime configuration file
 #[derive(Parser, PartialEq)]
-#[clap(name = "new", after_help = CONFIG_NEW_AFTER_HELP)]
 pub struct ConfigNewCommand {
     /// The path of the new configuration file
-    #[clap(index = 1, value_name = "FILE_PATH")]
+    #[arg(index = 1, value_name = "FILE_PATH")]
     path: Option<String>,
 }
 
diff --git a/src/commands/explore.rs b/src/commands/explore.rs
index 6b008253f..465a1d342 100644
--- a/src/commands/explore.rs
+++ b/src/commands/explore.rs
@@ -7,22 +7,21 @@ use wasmtime_cli_flags::CommonOptions;
 
 /// Explore the compilation of a WebAssembly module to native code.
 #[derive(Parser, PartialEq)]
-#[clap(name = "explore")]
 pub struct ExploreCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     target: Option<String>,
 
     /// The path of the WebAssembly module to compile
-    #[clap(required = true, value_name = "MODULE")]
+    #[arg(required = true, value_name = "MODULE")]
     module: PathBuf,
 
     /// The path of the explorer output (derived from the MODULE name if none
     /// provided)
-    #[clap(short, long)]
+    #[arg(short, long)]
     output: Option<PathBuf>,
 }
 
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 5f43c5a54..964a50bde 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -55,9 +55,8 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
 
 /// Runs a WebAssembly module
 #[derive(Parser, PartialEq)]
-#[structopt(name = "run")]
 pub struct RunCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     #[allow(missing_docs)]
     pub run: RunCommon,
 
@@ -67,7 +66,7 @@ pub struct RunCommand {
     /// host is made available within the guest. If specified as `HOST::GUEST`
     /// then the `HOST` directory is opened and made available as the name
     /// `GUEST` in the guest.
-    #[clap(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
+    #[arg(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
     pub dirs: Vec<(String, String)>,
 
     /// Pass an environment variable to the program.
@@ -77,15 +76,15 @@ pub struct RunCommand {
     /// form will set the environment variable named `FOO` to the same value it
     /// has in the calling process for the guest, or in other words it will
     /// cause the environment variable `FOO` to be inherited.
-    #[clap(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
+    #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
     pub vars: Vec<(String, Option<String>)>,
 
     /// The name of the function to run
-    #[clap(long, value_name = "FUNCTION")]
+    #[arg(long, value_name = "FUNCTION")]
     pub invoke: Option<String>,
 
     /// Load the given WebAssembly module before the main module
-    #[clap(
+    #[arg(
         long = "preload",
         number_of_values = 1,
         value_name = "NAME=MODULE_PATH",
@@ -98,7 +97,7 @@ pub struct RunCommand {
     /// Arguments passed to the wasm module will be configured as WASI CLI
     /// arguments unless the `--invoke` CLI argument is passed in which case
     /// arguments will be interpreted as arguments to the function specified.
-    #[clap(value_name = "WASM", trailing_var_arg = true, required = true)]
+    #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
     pub module_and_args: Vec<OsString>,
 }
 
@@ -778,6 +777,12 @@ impl RunCommand {
         if let Some(enable) = self.run.common.wasi.allow_ip_name_lookup {
             builder.allow_ip_name_lookup(enable);
         }
+        if let Some(enable) = self.run.common.wasi.tcp {
+            builder.allow_tcp(enable);
+        }
+        if let Some(enable) = self.run.common.wasi.udp {
+            builder.allow_udp(enable);
+        }
 
         store.data_mut().preview2_ctx = Some(Arc::new(builder.build()));
         Ok(())
@@ -791,7 +796,7 @@ struct Host {
 
     // Resource table for preview2 if the `preview2_ctx` is in use, otherwise
     // "just" an empty table.
-    preview2_table: Arc<preview2::Table>,
+    preview2_table: Arc<wasmtime::component::ResourceTable>,
 
     // State necessary for the preview1 implementation of WASI backed by the
     // preview2 host implementation. Only used with the `--preview2` flag right
@@ -810,11 +815,11 @@ struct Host {
 }
 
 impl preview2::WasiView for Host {
-    fn table(&self) -> &preview2::Table {
+    fn table(&self) -> &wasmtime::component::ResourceTable {
         &self.preview2_table
     }
 
-    fn table_mut(&mut self) -> &mut preview2::Table {
+    fn table_mut(&mut self) -> &mut wasmtime::component::ResourceTable {
         Arc::get_mut(&mut self.preview2_table).expect("preview2 is not compatible with threads")
     }
 
@@ -845,7 +850,7 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host {
         Arc::get_mut(ctx).expect("preview2 is not compatible with threads")
     }
 
-    fn table(&mut self) -> &mut preview2::Table {
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
         Arc::get_mut(&mut self.preview2_table).expect("preview2 is not compatible with threads")
     }
 }
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 629ec8b57..9cfc16e30 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -11,9 +11,7 @@ use std::{
 };
 use wasmtime::component::{InstancePre, Linker};
 use wasmtime::{Engine, Store, StoreLimits};
-use wasmtime_wasi::preview2::{
-    self, StreamError, StreamResult, Table, WasiCtx, WasiCtxBuilder, WasiView,
-};
+use wasmtime_wasi::preview2::{self, StreamError, StreamResult, WasiCtx, WasiCtxBuilder, WasiView};
 use wasmtime_wasi_http::io::TokioIo;
 use wasmtime_wasi_http::{
     bindings::http::types as http_types, body::HyperOutgoingBody, hyper_response_error,
@@ -24,7 +22,7 @@ use wasmtime_wasi_http::{
 use wasmtime_wasi_nn::WasiNnCtx;
 
 struct Host {
-    table: Table,
+    table: wasmtime::component::ResourceTable,
     ctx: WasiCtx,
     http: WasiHttpCtx,
 
@@ -35,11 +33,11 @@ struct Host {
 }
 
 impl WasiView for Host {
-    fn table(&self) -> &Table {
+    fn table(&self) -> &wasmtime::component::ResourceTable {
         &self.table
     }
 
-    fn table_mut(&mut self) -> &mut Table {
+    fn table_mut(&mut self) -> &mut wasmtime::component::ResourceTable {
         &mut self.table
     }
 
@@ -53,7 +51,7 @@ impl WasiView for Host {
 }
 
 impl WasiHttpView for Host {
-    fn table(&mut self) -> &mut Table {
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
         &mut self.table
     }
 
@@ -69,17 +67,16 @@ const DEFAULT_ADDR: std::net::SocketAddr = std::net::SocketAddr::new(
 
 /// Runs a WebAssembly module
 #[derive(Parser, PartialEq)]
-#[structopt(name = "serve")]
 pub struct ServeCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     run: RunCommon,
 
     /// Socket address for the web server to bind to.
-    #[clap(long = "addr", value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR )]
+    #[arg(long = "addr", value_name = "SOCKADDR", default_value_t = DEFAULT_ADDR )]
     addr: std::net::SocketAddr,
 
     /// The WebAssembly component to run.
-    #[clap(value_name = "WASM", required = true)]
+    #[arg(value_name = "WASM", required = true)]
     component: PathBuf,
 }
 
@@ -157,7 +154,7 @@ impl ServeCommand {
         });
 
         let mut host = Host {
-            table: Table::new(),
+            table: wasmtime::component::ResourceTable::new(),
             ctx: builder.build(),
             http: WasiHttpCtx,
 
diff --git a/src/commands/settings.rs b/src/commands/settings.rs
index 600a23454..9053b2cd6 100644
--- a/src/commands/settings.rs
+++ b/src/commands/settings.rs
@@ -9,14 +9,13 @@ use wasmtime_environ::{CompilerBuilder, FlagValue, Setting, SettingKind};
 
 /// Displays available Cranelift settings for a target.
 #[derive(Parser, PartialEq)]
-#[clap(name = "run")]
 pub struct SettingsCommand {
     /// The target triple to get the settings for; defaults to the host triple.
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     target: Option<String>,
 
     /// Switch output format to JSON
-    #[clap(long)]
+    #[arg(long)]
     json: bool,
 }
 
diff --git a/src/commands/wast.rs b/src/commands/wast.rs
index 669b817a5..1edeeb4dc 100644
--- a/src/commands/wast.rs
+++ b/src/commands/wast.rs
@@ -9,13 +9,12 @@ use wasmtime_wast::WastContext;
 
 /// Runs a WebAssembly test script file
 #[derive(Parser, PartialEq)]
-#[clap(name = "wast", version)]
 pub struct WastCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// The path of the WebAssembly test script to run
-    #[clap(required = true, value_name = "SCRIPT_FILE")]
+    #[arg(required = true, value_name = "SCRIPT_FILE")]
     scripts: Vec<PathBuf>,
 }
 
diff --git a/src/common.rs b/src/common.rs
index 2d5f68c6f..70782c0b3 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -37,7 +37,7 @@ impl RunTarget {
 /// Common command line arguments for run commands.
 #[derive(Parser, PartialEq)]
 pub struct RunCommon {
-    #[clap(flatten)]
+    #[command(flatten)]
     pub common: CommonOptions,
 
     /// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
@@ -46,7 +46,7 @@ pub struct RunCommon {
     /// is arbitrary user input. Only `wasmtime`-precompiled modules generated
     /// via the `wasmtime compile` command or equivalent should be passed as an
     /// argument with this option specified.
-    #[clap(long = "allow-precompiled")]
+    #[arg(long = "allow-precompiled")]
     pub allow_precompiled: bool,
 
     /// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest)
@@ -64,7 +64,7 @@ pub struct RunCommon {
     /// where `path` is where to write the profile and `interval` is the
     /// duration between samples. When used with `--wasm-timeout` the timeout
     /// will be rounded up to the nearest multiple of this interval.
-    #[clap(
+    #[arg(
         long,
         value_name = "STRATEGY",
         value_parser = Profile::parse,
diff --git a/src/old_cli.rs b/src/old_cli.rs
index ec73aab63..f6bbe5ff4 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -10,7 +10,7 @@ use std::time::Duration;
 
 /// Wasmtime WebAssembly Runtime
 #[derive(Parser)]
-#[clap(
+#[command(
     version,
     after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\
                   \n\
@@ -50,23 +50,23 @@ pub enum Subcommand {
 }
 
 #[derive(Parser)]
-#[structopt(name = "run", trailing_var_arg = true)]
+#[command(trailing_var_arg = true)]
 pub struct RunCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     common: CommonOptions,
 
     /// Allow unknown exports when running commands.
-    #[clap(long = "allow-unknown-exports")]
+    #[arg(long = "allow-unknown-exports")]
     allow_unknown_exports: bool,
 
     /// Allow the main module to import unknown functions, using an
     /// implementation that immediately traps, when running commands.
-    #[clap(long = "trap-unknown-imports")]
+    #[arg(long = "trap-unknown-imports")]
     trap_unknown_imports: bool,
 
     /// Allow the main module to import unknown functions, using an
     /// implementation that returns default values, when running commands.
-    #[clap(long = "default-values-unknown-imports")]
+    #[arg(long = "default-values-unknown-imports")]
     default_values_unknown_imports: bool,
 
     /// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
@@ -75,16 +75,16 @@ pub struct RunCommand {
     /// is arbitrary user input. Only `wasmtime`-precompiled modules generated
     /// via the `wasmtime compile` command or equivalent should be passed as an
     /// argument with this option specified.
-    #[clap(long = "allow-precompiled")]
+    #[arg(long = "allow-precompiled")]
     allow_precompiled: bool,
 
     /// Inherit environment variables and file descriptors following the
     /// systemd listen fd specification (UNIX only)
-    #[clap(long = "listenfd")]
+    #[arg(long = "listenfd")]
     listenfd: bool,
 
     /// Grant access to the given TCP listen socket
-    #[clap(
+    #[arg(
         long = "tcplisten",
         number_of_values = 1,
         value_name = "SOCKET ADDRESS"
@@ -92,7 +92,7 @@ pub struct RunCommand {
     tcplisten: Vec<String>,
 
     /// Grant access to the given host directory
-    #[clap(long = "dir", number_of_values = 1, value_name = "DIRECTORY")]
+    #[arg(long = "dir", number_of_values = 1, value_name = "DIRECTORY")]
     dirs: Vec<String>,
 
     /// Pass an environment variable to the program.
@@ -102,15 +102,15 @@ pub struct RunCommand {
     /// form will set the environment variable named `FOO` to the same value it
     /// has in the calling process for the guest, or in other words it will
     /// cause the environment variable `FOO` to be inherited.
-    #[clap(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
+    #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
     vars: Vec<(String, Option<String>)>,
 
     /// The name of the function to run
-    #[clap(long, value_name = "FUNCTION")]
+    #[arg(long, value_name = "FUNCTION")]
     invoke: Option<String>,
 
     /// Grant access to a guest directory mapped as a host directory
-    #[clap(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_map_dirs)]
+    #[arg(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_map_dirs)]
     map_dirs: Vec<(String, String)>,
 
     /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn.
@@ -121,11 +121,11 @@ pub struct RunCommand {
     /// an OpenVINO model named `bar`. Note that which model encodings are
     /// available is dependent on the backends implemented in the
     /// `wasmtime_wasi_nn` crate.
-    #[clap(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)]
+    #[arg(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)]
     graphs: Vec<(String, String)>,
 
     /// The path of the WebAssembly module to run
-    #[clap(
+    #[arg(
 		required = true,
         value_name = "MODULE",
         value_parser = OsStringValueParser::new().try_map(parse_module),
@@ -133,7 +133,7 @@ pub struct RunCommand {
     module: PathBuf,
 
     /// Load the given WebAssembly module before the main module
-    #[clap(
+    #[arg(
         long = "preload",
         number_of_values = 1,
         value_name = "NAME=MODULE_PATH",
@@ -142,7 +142,7 @@ pub struct RunCommand {
     preloads: Vec<(String, PathBuf)>,
 
     /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
-    #[clap(
+    #[arg(
         long = "wasm-timeout",
         value_name = "TIME",
         value_parser = parse_dur,
@@ -164,7 +164,7 @@ pub struct RunCommand {
     /// where `path` is where to write the profile and `interval` is the
     /// duration between samples. When used with `--wasm-timeout` the timeout
     /// will be rounded up to the nearest multiple of this interval.
-    #[clap(
+    #[arg(
         long,
         value_name = "STRATEGY",
         value_parser = parse_profile,
@@ -172,35 +172,35 @@ pub struct RunCommand {
     profile: Option<Profile>,
 
     /// Enable coredump generation after a WebAssembly trap.
-    #[clap(long = "coredump-on-trap", value_name = "PATH")]
+    #[arg(long = "coredump-on-trap", value_name = "PATH")]
     coredump_on_trap: Option<String>,
 
     // NOTE: this must come last for trailing varargs
     /// The arguments to pass to the module
-    #[clap(value_name = "ARGS")]
+    #[arg(value_name = "ARGS")]
     module_args: Vec<String>,
 
     /// Maximum size, in bytes, that a linear memory is allowed to reach.
     ///
     /// Growth beyond this limit will cause `memory.grow` instructions in
     /// WebAssembly modules to return -1 and fail.
-    #[clap(long, value_name = "BYTES")]
+    #[arg(long, value_name = "BYTES")]
     max_memory_size: Option<usize>,
 
     /// Maximum size, in table elements, that a table is allowed to reach.
-    #[clap(long)]
+    #[arg(long)]
     max_table_elements: Option<u32>,
 
     /// Maximum number of WebAssembly instances allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_instances: Option<usize>,
 
     /// Maximum number of WebAssembly tables allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_tables: Option<usize>,
 
     /// Maximum number of WebAssembly linear memories allowed to be created.
-    #[clap(long)]
+    #[arg(long)]
     max_memories: Option<usize>,
 
     /// Force a trap to be raised on `memory.grow` and `table.grow` failure
@@ -209,7 +209,7 @@ pub struct RunCommand {
     /// This is not necessarily a spec-compliant option to enable but can be
     /// useful for tracking down a backtrace of what is requesting so much
     /// memory, for example.
-    #[clap(long)]
+    #[arg(long)]
     trap_on_grow_failure: bool,
 
     /// Indicates that the implementation of WASI preview1 should be backed by
@@ -217,18 +217,18 @@ pub struct RunCommand {
     ///
     /// This will become the default in the future and this option will be
     /// removed. For now this is primarily here for testing.
-    #[clap(long)]
+    #[arg(long)]
     preview2: bool,
 
     /// Enables memory error checking.
     ///
     /// See wmemcheck.md for documentation on how to use.
-    #[clap(long)]
+    #[arg(long)]
     wmemcheck: bool,
 
     /// Flag for WASI preview2 to inherit the host's network within the guest so
     /// it has full access to all addresses/ports/etc.
-    #[clap(long)]
+    #[arg(long)]
     inherit_network: bool,
 }
 fn parse_module(s: OsString) -> anyhow::Result<PathBuf> {
@@ -315,23 +315,23 @@ fn parse_profile(s: &str) -> Result<Profile> {
 /// Compiles a WebAssembly module.
 #[derive(Parser)]
 pub struct CompileCommand {
-    #[clap(flatten)]
+    #[command(flatten)]
     pub common: CommonOptions,
 
     /// The target triple; default is the host triple
-    #[clap(long, value_name = "TARGET")]
+    #[arg(long, value_name = "TARGET")]
     pub target: Option<String>,
 
     /// The path of the output compiled module; defaults to <MODULE>.cwasm
-    #[clap(short = 'o', long, value_name = "OUTPUT")]
+    #[arg(short = 'o', long, value_name = "OUTPUT")]
     pub output: Option<PathBuf>,
 
     /// The directory path to write clif files into, one clif file per wasm function.
-    #[clap(long = "emit-clif", value_name = "PATH")]
+    #[arg(long = "emit-clif", value_name = "PATH")]
     pub emit_clif: Option<PathBuf>,
 
     /// The path of the WebAssembly to compile
-    #[clap(index = 1, value_name = "MODULE")]
+    #[arg(index = 1, value_name = "MODULE")]
     pub module: PathBuf,
 }
 
@@ -340,45 +340,45 @@ pub struct CompileCommand {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 pub struct CommonOptions {
     /// Use specified configuration file
-    #[clap(long, value_name = "CONFIG_PATH")]
+    #[arg(long, value_name = "CONFIG_PATH")]
     pub config: Option<PathBuf>,
 
     /// Disable logging
-    #[clap(long, conflicts_with = "log_to_files")]
+    #[arg(long, conflicts_with = "log_to_files")]
     pub disable_logging: bool,
 
     /// Log to per-thread log files instead of stderr
-    #[clap(long)]
+    #[arg(long)]
     pub log_to_files: bool,
 
     /// Generate debug information
-    #[clap(short = 'g')]
+    #[arg(short = 'g')]
     pub debug_info: bool,
 
     /// Disable cache system
-    #[clap(long)]
+    #[arg(long)]
     pub disable_cache: bool,
 
     /// Disable parallel compilation
-    #[clap(long)]
+    #[arg(long)]
     pub disable_parallel_compilation: bool,
 
     /// Enable or disable WebAssembly features
-    #[clap(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)]
+    #[arg(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)]
     pub wasm_features: Option<WasmFeatures>,
 
     /// Enable or disable WASI modules
-    #[clap(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)]
+    #[arg(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)]
     pub wasi_modules: Option<WasiModules>,
 
     /// Generate jitdump file (supported on --features=profiling build)
     /// Run optimization passes on translated functions, on by default
-    #[clap(short = 'O', long)]
+    #[arg(short = 'O', long)]
     pub optimize: bool,
 
     /// Optimization level for generated functions
     /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
-    #[clap(
+    #[arg(
         long,
         value_name = "LEVEL",
         value_parser = parse_opt_level,
@@ -388,7 +388,7 @@ pub struct CommonOptions {
 
     /// Set a Cranelift setting to a given value.
     /// Use `wasmtime settings` to list Cranelift settings for a target.
-    #[clap(
+    #[arg(
         long = "cranelift-set",
         value_name = "NAME=VALUE",
         number_of_values = 1,
@@ -399,7 +399,7 @@ pub struct CommonOptions {
 
     /// Enable a Cranelift boolean setting or preset.
     /// Use `wasmtime settings` to list Cranelift settings for a target.
-    #[clap(
+    #[arg(
         long,
         value_name = "SETTING",
         number_of_values = 1,
@@ -409,32 +409,32 @@ pub struct CommonOptions {
 
     /// Maximum size in bytes of wasm memory before it becomes dynamically
     /// relocatable instead of up-front-reserved.
-    #[clap(long, value_name = "MAXIMUM")]
+    #[arg(long, value_name = "MAXIMUM")]
     pub static_memory_maximum_size: Option<u64>,
 
     /// Force using a "static" style for all wasm memories
-    #[clap(long)]
+    #[arg(long)]
     pub static_memory_forced: bool,
 
     /// Byte size of the guard region after static memories are allocated
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub static_memory_guard_size: Option<u64>,
 
     /// Byte size of the guard region after dynamic memories are allocated
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub dynamic_memory_guard_size: Option<u64>,
 
     /// Bytes to reserve at the end of linear memory for growth for dynamic
     /// memories.
-    #[clap(long, value_name = "SIZE")]
+    #[arg(long, value_name = "SIZE")]
     pub dynamic_memory_reserved_for_growth: Option<u64>,
 
     /// Enable Cranelift's internal debug verifier (expensive)
-    #[clap(long)]
+    #[arg(long)]
     pub enable_cranelift_debug_verifier: bool,
 
     /// Enable Cranelift's internal NaN canonicalization
-    #[clap(long)]
+    #[arg(long)]
     pub enable_cranelift_nan_canonicalization: bool,
 
     /// Enable execution fuel with N units fuel, where execution will trap after
@@ -444,33 +444,33 @@ pub struct CommonOptions {
     /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
     /// execution cost associated with them involves other instructions which do
     /// consume fuel.
-    #[clap(long, value_name = "N")]
+    #[arg(long, value_name = "N")]
     pub fuel: Option<u64>,
 
     /// Executing wasm code will yield when a global epoch counter
     /// changes, allowing for async operation without blocking the
     /// executor.
-    #[clap(long)]
+    #[arg(long)]
     pub epoch_interruption: bool,
 
     /// Disable the on-by-default address map from native code to wasm code
-    #[clap(long)]
+    #[arg(long)]
     pub disable_address_map: bool,
 
     /// Disable the default of attempting to initialize linear memory via a
     /// copy-on-write mapping
-    #[clap(long)]
+    #[arg(long)]
     pub disable_memory_init_cow: bool,
 
     /// Enable the pooling allocator, in place of the on-demand
     /// allocator.
     #[cfg(feature = "pooling-allocator")]
-    #[clap(long)]
+    #[arg(long)]
     pub pooling_allocator: bool,
 
     /// Maximum stack size, in bytes, that wasm is allowed to consume before a
     /// stack overflow is reported.
-    #[clap(long)]
+    #[arg(long)]
     pub max_wasm_stack: Option<usize>,
 
     /// Whether or not to force deterministic and host-independent behavior of
@@ -481,14 +481,14 @@ pub struct CommonOptions {
     /// of these instructions to match the deterministic behavior classified in
     /// the specification. Note that enabling this option may come at a
     /// performance cost.
-    #[clap(long)]
+    #[arg(long)]
     pub relaxed_simd_deterministic: bool,
 
     /// Explicitly specify the name of the compiler to use for WebAssembly.
     ///
     /// Currently only `cranelift` and `winch` are supported, but not all builds
     /// of Wasmtime have both built in.
-    #[clap(long)]
+    #[arg(long)]
     pub compiler: Option<String>,
 }
 
diff --git a/winch/src/compile.rs b/winch/src/compile.rs
index e6c3040e3..e6d8d7173 100644
--- a/winch/src/compile.rs
+++ b/winch/src/compile.rs
@@ -17,7 +17,7 @@ pub struct Options {
     input: PathBuf,
 
     /// The target architecture.
-    #[clap(long = "target")]
+    #[arg(long = "target")]
     target: String,
 }
 


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/arithmetic.clif b/cranelift/filetests/filetests/egraph/arithmetic.clif
index 542fde629..1abdeae1e 100644
--- a/cranelift/filetests/filetests/egraph/arithmetic.clif
+++ b/cranelift/filetests/filetests/egraph/arithmetic.clif
@@ -20,6 +20,42 @@ block0(v0: i32):
     ; check: return v0
 }
 
+function %add_ineg(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+    v2 = ineg v1
+    v3 = iadd v0, v2
+    return v3
+    ; check: v4 = isub v0, v1
+    ; check: return v4
+}
+
+function %ineg_add(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+    v2 = ineg v0
+    v3 = iadd v2, v1
+    return v3
+    ; check: v4 = isub v1, v0
+    ; check: return v4
+}
+
+function %sub_ineg(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+    v2 = ineg v1
+    v3 = isub v0, v2
+    return v3
+    ; check: v4 = iadd v0, v1
+    ; check: return v4
+}
+
+function %sub_then_ineg(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+    v2 = isub v0, v1
+    v3 = ineg v2
+    return v3
+    ; check: v4 = isub v1, v0
+    ; check: return v4
+}
+
 function %imul_ineg_cancel(i32, i32) -> i32 {
 block0(v0: i32, v1: i32):
     v2 = ineg v0
@@ -125,8 +161,8 @@ block0(v0: i32):
     return v3
 }
 
-; check: v4 = ineg v0
-; check: return v4
+; check: v5 = ineg v0
+; check: return v5
 
 function %ineg_not_minus_neg_1(i32) -> i32 {
 block0(v0: i32):
@@ -136,8 +172,8 @@ block0(v0: i32):
     return v3
 }
 
-; check: v4 = ineg v0
-; check: return v4
+; check: v6 = ineg v0
+; check: return v6
 
 function %ineg_not_sub_one(i32) -> i32 {
 block0(v0: i32):
@@ -172,7 +208,17 @@ block0(v0: i32):
 ; check: v6 = ineg v0
 ; check: return v6
 
+function %byte_sub_smax_twice(i8) -> i8 {
+block0(v0: i8):
+    v1 = iconst.i8 127
+    v2 = isub v0, v1
+    v3 = isub v2, v1
+    return v3
+}
 
+; check: v6 = iconst.i8 2
+; check: v7 = iadd v0, v6  ; v6 = 2
+; check: return v7
 
 function %double_fneg(f32) -> f32 {
 block0(v1: f32):
diff --git a/cranelift/filetests/filetests/egraph/cprop.clif b/cranelift/filetests/filetests/egraph/cprop.clif
index 6de9aa695..6c2218a49 100644
--- a/cranelift/filetests/filetests/egraph/cprop.clif
+++ b/cranelift/filetests/filetests/egraph/cprop.clif
@@ -260,3 +260,25 @@ block0:
 
 ; check: v3 = iconst.i32 0xfff6
 ; nextln: return v3
+
+function %sub_u8max(i8) -> i8 {
+block0(v0: i8):
+    v1 = iconst.i8 255
+    v2 = isub v0, v1
+    return v2
+}
+
+; check: v3 = iconst.i8 1
+; nextln: v4 = iadd v0, v3  ; v3 = 1
+; nextln: return v4
+
+function %sub_u32max(i32) -> i32 {
+block0(v0: i32):
+    v1 = iconst.i32 0xFFFF_FFFF
+    v2 = isub v0, v1
+    return v2
+}
+
+; check: v3 = iconst.i32 1
+; nextln: v4 = iadd v0, v3  ; v3 = 1
+; nextln: return v4
diff --git a/cranelift/filetests/filetests/egraph/extends.clif b/cranelift/filetests/filetests/egraph/extends.clif
index 7ebb2642f..871b29ffa 100644
--- a/cranelift/filetests/filetests/egraph/extends.clif
+++ b/cranelift/filetests/filetests/egraph/extends.clif
@@ -54,6 +54,28 @@ block0(v1: i16):
 ; check: v4 = sextend.i64 v1
 ; check: return v4
 
+function %suextend_icmp(i16, i16) -> i64 {
+block0(v0: i16, v1: i16):
+    v2 = icmp slt v0, v1
+    v3 = uextend.i32 v2
+    v4 = sextend.i64 v3
+    return v4
+}
+
+; check: v5 = uextend.i64 v2
+; check: return v5
+
+function %usextend_icmp(i16, i16) -> i64 {
+block0(v0: i16, v1: i16):
+    v2 = icmp uge v0, v1
+    v3 = sextend.i32 v2
+    v4 = uextend.i64 v3
+    return v4
+}
+
+; check: v7 = uextend.i64 v2
+; check: return v7
+
 function %sextend_then_reduce(i16) -> i16 {
 block0(v1: i16):
     v2 = sextend.i32 v1
@@ -71,3 +93,27 @@ block0(v1: i32):
 }
 
 ; check: return v1
+
+function %sextend_then_slt_zero(i8) -> i8 {
+block0(v0: i8):
+    v1 = sextend.i16 v0
+    v2 = iconst.i16 0
+    v3 = icmp slt v1, v2
+    return v3
+}
+
+; check: v4 = iconst.i8 0
+; check: v5 = icmp slt v0, v4
+; check: return v5
+
+function %sextend_then_ne_zero(i8) -> i8 {
+block0(v0: i8):
+    v1 = sextend.i16 v0
+    v2 = iconst.i16 0
+    v3 = icmp ne v1, v2
+    return v3
+}
+
+; check: v4 = iconst.i8 0
+; check: v5 = icmp ne v0, v4
+; check: return v5
diff --git a/cranelift/filetests/filetests/egraph/shifts.clif b/cranelift/filetests/filetests/egraph/shifts.clif
index a9b321b20..c0df30564 100644
--- a/cranelift/filetests/filetests/egraph/shifts.clif
+++ b/cranelift/filetests/filetests/egraph/shifts.clif
@@ -701,8 +701,8 @@ block0(v0: i8):
 }
 
 ; check: v2 = iconst.i8 1
-; check: v21 = rotr v0, v2  ; v2 = 1
-; check: return v21
+; check: v16 = rotr v0, v2  ; v2 = 1
+; check: return v16
 
 function %rotl_rotr_add(i8, i8, i8) -> i8 {
 block0(v0: i8, v1: i8, v2: i8):
@@ -725,8 +725,8 @@ block0(v0: i8):
 }
 
 ; check: v2 = iconst.i8 1
-; check: v21 = rotl v0, v2  ; v2 = 1
-; check: return v21
+; check: v16 = rotl v0, v2  ; v2 = 1
+; check: return v16
 
 function %rotl_rotr_add(i8, i8, i8) -> i8 {
 block0(v0: i8, v1: i8, v2: i8):
diff --git a/cranelift/filetests/filetests/egraph/spaceship.clif b/cranelift/filetests/filetests/egraph/spaceship.clif
new file mode 100644
index 000000000..6484f8884
--- /dev/null
+++ b/cranelift/filetests/filetests/egraph/spaceship.clif
@@ -0,0 +1,480 @@
+test optimize
+set opt_level=speed
+target x86_64
+target aarch64
+target s390x
+target riscv64
+
+function %cmp_s1a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp slt v0, v1
+    v3 = icmp eq v0, v1
+    v4 = iconst.i8 -1
+    v5 = iconst.i8 0
+    v6 = iconst.i8 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v11 = icmp sgt v0, v1
+    ; check: v12 = icmp slt v0, v1
+    ; check: v13 = isub v11, v12
+    ; check: return v13
+}
+
+function %cmp_s1b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp slt v0, v1
+    v3 = icmp sle v0, v1
+    v4 = iconst.i16 -1
+    v5 = iconst.i16 0
+    v6 = iconst.i16 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v12 = icmp slt v0, v1
+    ; check: v13 = isub v9, v12
+    ; check: v14 = sextend.i16 v13
+    ; check: return v14
+}
+
+function %cmp_s2a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp slt v0, v1
+    v3 = icmp ne v0, v1
+    v4 = iconst.i8 -1
+    v5 = iconst.i8 +1
+    v6 = iconst.i8 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v10 = icmp sgt v0, v1
+    ; check: v11 = icmp slt v0, v1
+    ; check: v12 = isub v10, v11
+    ; check: return v12
+}
+
+function %cmp_s2b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp slt v0, v1
+    v3 = icmp sgt v0, v1
+    v4 = iconst.i16 -1
+    v5 = iconst.i16 +1
+    v6 = iconst.i16 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v11 = icmp sgt v0, v1
+    ; check: v12 = icmp slt v0, v1
+    ; check: v13 = isub v11, v12
+    ; check: v14 = sextend.i16 v13
+    ; check: return v14
+}
+
+function %cmp_s3a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp slt v0, v1
+    v4 = iconst.i8 0
+    v5 = iconst.i8 -1
+    v6 = iconst.i8 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_s3b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp sle v0, v1
+    v4 = iconst.i16 0
+    v5 = iconst.i16 -1
+    v6 = iconst.i16 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_s4a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp sgt v0, v1
+    v4 = iconst.i8 0
+    v5 = iconst.i8 +1
+    v6 = iconst.i8 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_s4b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp sge v0, v1
+    v4 = iconst.i16 0
+    v5 = iconst.i16 +1
+    v6 = iconst.i16 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_s5a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp sgt v0, v1
+    v3 = icmp slt v0, v1
+    v4 = iconst.i8 +1
+    v5 = iconst.i8 -1
+    v6 = iconst.i8 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_s5b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp sgt v0, v1
+    v3 = icmp ne v0, v1
+    v4 = iconst.i16 +1
+    v5 = iconst.i16 -1
+    v6 = iconst.i16 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_s6a(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp sgt v0, v1
+    v3 = icmp sge v0, v1
+    v4 = iconst.i8 +1
+    v5 = iconst.i8 0
+    v6 = iconst.i8 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_s6b(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp sgt v0, v1
+    v3 = icmp eq v0, v1
+    v4 = iconst.i16 +1
+    v5 = iconst.i16 0
+    v6 = iconst.i16 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp sgt v0, v1
+    ; check: v10 = icmp slt v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+;; And again for unsigned...
+
+function %cmp_u1a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ult v0, v1
+    v3 = icmp eq v0, v1
+    v4 = iconst.i16 -1
+    v5 = iconst.i16 0
+    v6 = iconst.i16 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v12 = icmp ugt v0, v1
+    ; check: v13 = icmp ult v0, v1
+    ; check: v14 = isub v12, v13
+    ; check: v15 = sextend.i16 v14
+    ; check: return v15
+}
+
+function %cmp_u1b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ult v0, v1
+    v3 = icmp ule v0, v1
+    v4 = iconst.i8 -1
+    v5 = iconst.i8 0
+    v6 = iconst.i8 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v11 = icmp ult v0, v1
+    ; check: v12 = isub v9, v11
+    ; check: return v12
+}
+
+function %cmp_u2a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ult v0, v1
+    v3 = icmp ne v0, v1
+    v4 = iconst.i16 -1
+    v5 = iconst.i16 +1
+    v6 = iconst.i16 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v11 = icmp ugt v0, v1
+    ; check: v12 = icmp ult v0, v1
+    ; check: v13 = isub v11, v12
+    ; check: v14 = sextend.i16 v13
+    ; check: return v14
+}
+
+function %cmp_u2b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ult v0, v1
+    v3 = icmp ugt v0, v1
+    v4 = iconst.i8 -1
+    v5 = iconst.i8 +1
+    v6 = iconst.i8 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v10 = icmp ugt v0, v1
+    ; check: v11 = icmp ult v0, v1
+    ; check: v12 = isub v10, v11
+    ; check: return v12
+}
+
+function %cmp_u3a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp ult v0, v1
+    v4 = iconst.i16 0
+    v5 = iconst.i16 -1
+    v6 = iconst.i16 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_u3b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp ule v0, v1
+    v4 = iconst.i8 0
+    v5 = iconst.i8 -1
+    v6 = iconst.i8 +1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_u4a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp ugt v0, v1
+    v4 = iconst.i16 0
+    v5 = iconst.i16 +1
+    v6 = iconst.i16 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_u4b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp eq v0, v1
+    v3 = icmp uge v0, v1
+    v4 = iconst.i8 0
+    v5 = iconst.i8 +1
+    v6 = iconst.i8 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_u5a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ult v0, v1
+    v4 = iconst.i16 +1
+    v5 = iconst.i16 -1
+    v6 = iconst.i16 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_u5b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ne v0, v1
+    v4 = iconst.i8 +1
+    v5 = iconst.i8 -1
+    v6 = iconst.i8 0
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+function %cmp_u6a(i32, i32) -> i16 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ugt v0, v1
+    v3 = icmp uge v0, v1
+    v4 = iconst.i16 +1
+    v5 = iconst.i16 0
+    v6 = iconst.i16 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: v12 = sextend.i16 v11
+    ; check: return v12
+}
+
+function %cmp_u6b(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp ugt v0, v1
+    v3 = icmp eq v0, v1
+    v4 = iconst.i8 +1
+    v5 = iconst.i8 0
+    v6 = iconst.i8 -1
+    v7 = select v3, v5, v6
+    v8 = select v2, v4, v7
+    return v8
+    ; check: v9 = icmp ugt v0, v1
+    ; check: v10 = icmp ult v0, v1
+    ; check: v11 = isub v9, v10
+    ; check: return v11
+}
+
+;; Then a few of the simplifications
+
+function %ult_via_cmp(i8, i8) -> i8 {
+block0(v0: i8, v1: i8):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ult v0, v1
+    v4 = isub v2, v3
+    v5 = iconst.i8 0
+    v6 = icmp slt v4, v5
+    return v6
+    ; check: v3 = icmp ult v0, v1
+    ; check: return v3
+}
+
+function %sle_via_cmp(i16, i16) -> i8 {
+block0(v0: i16, v1: i16):
+    v2 = icmp sgt v0, v1
+    v3 = icmp slt v0, v1
+    v4 = isub v2, v3
+    v5 = iconst.i8 0
+    v6 = icmp sle v4, v5
+    return v6
+    ; check: v7 = icmp sle v0, v1
+    ; check: return v7
+}
+
+function %sgt_via_cmp(i32, i32) -> i8 {
+block0(v0: i32, v1: i32):
+    v2 = icmp sgt v0, v1
+    v3 = icmp slt v0, v1
+    v4 = isub v2, v3
+    v5 = iconst.i8 0
+    v6 = icmp sgt v4, v5
+    return v6
+    ; check: v7 = icmp sgt v0, v1
+    ; check: return v7
+}
+
+function %uge_via_cmp(i64, i64) -> i8 {
+block0(v0: i64, v1: i64):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ult v0, v1
+    v4 = isub v2, v3
+    v5 = iconst.i8 0
+    v6 = icmp sge v4, v5
+    return v6
+    ; check: v7 = icmp uge v0, v1
+    ; check: return v7
+}
+
+function %eq_via_cmp(i16, i16) -> i8 {
+block0(v0: i16, v1: i16):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ult v0, v1
+    v4 = isub v2, v3
+    v5 = sextend.i32 v4
+    v6 = iconst.i32 0
+    v7 = icmp eq v5, v6
+    return v7
+    ; check: v10 = icmp eq v0, v1
+    ; check: return v10
+}
+
+function %ne_via_cmp(i16, i16) -> i8 {
+block0(v0: i16, v1: i16):
+    v2 = icmp ugt v0, v1
+    v3 = icmp ult v0, v1
+    v4 = isub v2, v3
+    v5 = sextend.i32 v4
+    v6 = iconst.i32 0
+    v7 = icmp ne v5, v6
+    return v7
+    ; check: v10 = icmp ne v0, v1
+    ; check: return v10
+}
diff --git a/cranelift/filetests/filetests/pcc/succeed/const.clif b/cranelift/filetests/filetests/pcc/succeed/const.clif
index da0c8a086..d0c381c5c 100644
--- a/cranelift/filetests/filetests/pcc/succeed/const.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/const.clif
@@ -16,3 +16,15 @@ block0:
     v8 ! range(64, 0xffff_0000_0000_ffff, 0xffff_0000_0000_ffff) = iconst.i64 0xffff_0000_0000_ffff
     return
 }
+
+function %f1() -> i32 {
+block0:
+    v0 = iconst.i32 0x10_0010
+    return v0
+}
+
+function %f2() -> i64 {
+block0:
+    v0 = iconst.i64 0x9_ffff_ffff
+    return v0
+}
diff --git a/crates/component-macro/tests/codegen/multiversion/root.wit b/crates/component-macro/tests/codegen/multiversion/root.wit
index 52225f02b..b3bb3c80d 100644
--- a/crates/component-macro/tests/codegen/multiversion/root.wit
+++ b/crates/component-macro/tests/codegen/multiversion/root.wit
@@ -3,5 +3,6 @@ package foo:bar;
 world foo {
   import my:dep/a@0.1.0;
   import my:dep/a@0.2.0;
+  export my:dep/a@0.1.0;
   export my:dep/a@0.2.0;
 }
diff --git a/crates/test-programs/src/bin/cli_no_ip_name_lookup.rs b/crates/test-programs/src/bin/cli_no_ip_name_lookup.rs
new file mode 100644
index 000000000..c0aeb1329
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_no_ip_name_lookup.rs
@@ -0,0 +1,15 @@
+//! This test assumes that it will be run without ip lookup support enabled
+use test_programs::wasi::sockets::{
+    ip_name_lookup::{ErrorCode, IpAddress},
+    network::Network,
+};
+
+fn main() {
+    let res = resolve("example.com");
+    eprintln!("Result of resolve: {res:?}");
+    assert!(matches!(res, Err(ErrorCode::PermanentResolverFailure)));
+}
+
+fn resolve(name: &str) -> Result<Vec<IpAddress>, ErrorCode> {
+    Network::default().permissive_blocking_resolve_addresses(name)
+}
diff --git a/crates/test-programs/src/bin/cli_no_tcp.rs b/crates/test-programs/src/bin/cli_no_tcp.rs
new file mode 100644
index 000000000..58ca3de89
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_no_tcp.rs
@@ -0,0 +1,28 @@
+//! This test assumes that it will be run without tcp support enabled
+use test_programs::wasi::sockets::{
+    network::IpAddress,
+    tcp::{ErrorCode, IpAddressFamily, IpSocketAddress, Network, TcpSocket},
+};
+
+fn main() {
+    let net = Network::default();
+    let family = IpAddressFamily::Ipv4;
+    let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321);
+    let sock = TcpSocket::new(family).unwrap();
+
+    let bind = sock.blocking_bind(&net, remote1);
+    eprintln!("Result of binding: {bind:?}");
+    assert!(matches!(bind, Err(ErrorCode::AccessDenied)));
+
+    let listen = sock.blocking_listen();
+    eprintln!("Result of listen: {listen:?}");
+    assert!(matches!(listen, Err(ErrorCode::AccessDenied)));
+
+    let connect = sock.blocking_connect(&net, remote1);
+    eprintln!("Result of connect: {connect:?}");
+    assert!(matches!(connect, Err(ErrorCode::AccessDenied)));
+
+    let accept = sock.blocking_accept();
+    eprintln!("Result of accept: {accept:?}");
+    assert!(matches!(accept, Err(ErrorCode::AccessDenied)));
+}
diff --git a/crates/test-programs/src/bin/cli_no_udp.rs b/crates/test-programs/src/bin/cli_no_udp.rs
new file mode 100644
index 000000000..1dd024061
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_no_udp.rs
@@ -0,0 +1,16 @@
+//! This test assumes that it will be run without udp support enabled
+use test_programs::wasi::sockets::{
+    network::IpAddress,
+    udp::{ErrorCode, IpAddressFamily, IpSocketAddress, Network, UdpSocket},
+};
+
+fn main() {
+    let net = Network::default();
+    let family = IpAddressFamily::Ipv4;
+    let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321);
+    let sock = UdpSocket::new(family).unwrap();
+
+    let bind = sock.blocking_bind(&net, remote1);
+    eprintln!("Result of binding: {bind:?}");
+    assert!(matches!(bind, Err(ErrorCode::AccessDenied)));
+}
diff --git a/crates/test-programs/src/bin/preview2_adapter_badfd.rs b/crates/test-programs/src/bin/preview2_adapter_badfd.rs
new file mode 100644
index 000000000..e437dd668
--- /dev/null
+++ b/crates/test-programs/src/bin/preview2_adapter_badfd.rs
@@ -0,0 +1,46 @@
+fn main() {
+    #[link(wasm_import_module = "wasi_snapshot_preview1")]
+    extern "C" {
+        #[cfg_attr(target_arch = "wasm32", link_name = "adapter_open_badfd")]
+        fn adapter_open_badfd(fd: *mut u32) -> wasi::Errno;
+
+        #[cfg_attr(target_arch = "wasm32", link_name = "adapter_close_badfd")]
+        fn adapter_close_badfd(fd: u32) -> wasi::Errno;
+    }
+
+    unsafe {
+        let mut fd = 0;
+        assert_eq!(adapter_open_badfd(&mut fd), wasi::ERRNO_SUCCESS);
+
+        assert_eq!(wasi::fd_close(fd), Err(wasi::ERRNO_BADF));
+
+        assert_eq!(wasi::fd_fdstat_get(fd).map(drop), Err(wasi::ERRNO_BADF));
+
+        assert_eq!(wasi::fd_fdstat_set_rights(fd, 0, 0), Err(wasi::ERRNO_BADF));
+
+        let mut buffer = [0_u8; 1];
+        assert_eq!(
+            wasi::fd_read(
+                fd,
+                &[wasi::Iovec {
+                    buf: buffer.as_mut_ptr(),
+                    buf_len: 1
+                }]
+            ),
+            Err(wasi::ERRNO_BADF)
+        );
+
+        assert_eq!(
+            wasi::fd_write(
+                fd,
+                &[wasi::Ciovec {
+                    buf: buffer.as_ptr(),
+                    buf_len: 1
+                }]
+            ),
+            Err(wasi::ERRNO_BADF)
+        );
+
+        assert_eq!(adapter_close_badfd(fd), wasi::ERRNO_SUCCESS);
+    }
+}
diff --git a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
index dbad2e807..f62f48cbc 100644
--- a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
+++ b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
@@ -1,5 +1,5 @@
+use test_programs::wasi::sockets::instance_network::Network;
 use test_programs::wasi::sockets::network::{ErrorCode, IpAddress};
-use test_programs::wasi::sockets::*;
 
 fn main() {
     // Valid domains
@@ -50,19 +50,7 @@ fn main() {
 }
 
 fn resolve(name: &str) -> Result<Vec<IpAddress>, ErrorCode> {
-    let network = instance_network::instance_network();
-
-    match network.blocking_resolve_addresses(name) {
-        // The following error codes signal that the input passed validation
-        // and a lookup was actually attempted, but failed. Ignore these to
-        // make the CI tests less flaky:
-        Err(
-            ErrorCode::NameUnresolvable
-            | ErrorCode::TemporaryResolverFailure
-            | ErrorCode::PermanentResolverFailure,
-        ) => Ok(vec![]),
-        r => r,
-    }
+    Network::default().permissive_blocking_resolve_addresses(name)
 }
 
 fn resolve_one(name: &str) -> Result<IpAddress, ErrorCode> {
diff --git a/crates/test-programs/src/sockets.rs b/crates/test-programs/src/sockets.rs
index cea4f50ba..e4dc2d58b 100644
--- a/crates/test-programs/src/sockets.rs
+++ b/crates/test-programs/src/sockets.rs
@@ -80,6 +80,21 @@ impl Network {
             }
         }
     }
+
+    /// Same as `Network::blocking_resolve_addresses` but ignores post validation errors
+    ///
+    /// The ignored error codes signal that the input passed validation
+    /// and a lookup was actually attempted, but failed. These are ignored to
+    /// make the CI tests less flaky.
+    pub fn permissive_blocking_resolve_addresses(
+        &self,
+        name: &str,
+    ) -> Result<Vec<IpAddress>, ErrorCode> {
+        match self.blocking_resolve_addresses(name) {
+            Err(ErrorCode::NameUnresolvable | ErrorCode::TemporaryResolverFailure) => Ok(vec![]),
+            r => r,
+        }
+    }
 }
 
 impl TcpSocket {
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index 002e28b79..f078a30de 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -8,12 +8,10 @@ use sha2::{Digest, Sha256};
 use std::{collections::HashMap, iter, net::Ipv4Addr, str, sync::Arc};
 use tokio::task;
 use wasmtime::{
-    component::{Component, Linker, Resource},
+    component::{Component, Linker, Resource, ResourceTable},
     Config, Engine, Store,
 };
-use wasmtime_wasi::preview2::{
-    self, pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView,
-};
+use wasmtime_wasi::preview2::{self, pipe::MemoryOutputPipe, WasiCtx, WasiCtxBuilder, WasiView};
 use wasmtime_wasi_http::{
     bindings::http::types::ErrorCode,
     body::HyperIncomingBody,
@@ -31,7 +29,7 @@ type RequestSender = Arc<
 >;
 
 struct Ctx {
-    table: Table,
+    table: ResourceTable,
     wasi: WasiCtx,
     http: WasiHttpCtx,
     stdout: MemoryOutputPipe,
@@ -40,10 +38,10 @@ struct Ctx {
 }
 
 impl WasiView for Ctx {
-    fn table(&self) -> &Table {
+    fn table(&self) -> &ResourceTable {
         &self.table
     }
-    fn table_mut(&mut self) -> &mut Table {
+    fn table_mut(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
     fn ctx(&self) -> &WasiCtx {
@@ -59,7 +57,7 @@ impl WasiHttpView for Ctx {
         &mut self.http
     }
 
-    fn table(&mut self) -> &mut Table {
+    fn table(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
 
@@ -89,7 +87,7 @@ fn store(engine: &Engine, server: &Server) -> Store<Ctx> {
     builder.stderr(stderr.clone());
     builder.env("HTTP_SERVER", server.addr().to_string());
     let ctx = Ctx {
-        table: Table::new(),
+        table: ResourceTable::new(),
         wasi: builder.build(),
         http: WasiHttpCtx {},
         stderr,
@@ -132,7 +130,7 @@ async fn run_wasi_http(
 ) -> anyhow::Result<Result<hyper::Response<Collected<Bytes>>, ErrorCode>> {
     let stdout = MemoryOutputPipe::new(4096);
     let stderr = MemoryOutputPipe::new(4096);
-    let table = Table::new();
+    let table = ResourceTable::new();
 
     let mut config = Config::new();
     config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
diff --git a/crates/wasi-http/wit/test.wit b/crates/wasi-http/wit/test.wit
index 68dfaeaf3..f3cc6151b 100644
--- a/crates/wasi-http/wit/test.wit
+++ b/crates/wasi-http/wit/test.wit
@@ -2,7 +2,7 @@ package wasmtime:wasi;
 
 // only used as part of `test-programs`
 world test-reactor {
-  include wasi:cli/reactor@0.2.0-rc-2023-11-10;
+  include wasi:cli/imports@0.2.0-rc-2023-12-05;
 
   export add-strings: func(s: list<string>) -> u32;
   export get-strings: func() -> list<string>;
@@ -16,7 +16,7 @@ world test-reactor {
 }
 
 world test-command {
-  include wasi:cli/reactor@0.2.0-rc-2023-11-10;
-  import wasi:http/types@0.2.0-rc-2023-11-10;
-  import wasi:http/outgoing-handler@0.2.0-rc-2023-11-10;
+  include wasi:cli/imports@0.2.0-rc-2023-12-05;
+  import wasi:http/types@0.2.0-rc-2023-12-05;
+  import wasi:http/outgoing-handler@0.2.0-rc-2023-12-05;
 }
diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs
index 4abf8a119..413713aa1 100644
--- a/crates/wasi/tests/all/api.rs
+++ b/crates/wasi/tests/all/api.rs
@@ -4,26 +4,25 @@ use cap_std::fs::Dir;
 use std::io::Write;
 use std::sync::Mutex;
 use std::time::Duration;
-use wasmtime::component::{Component, Linker};
+use wasmtime::component::{Component, Linker, ResourceTable};
 use wasmtime::{Config, Engine, Store};
 use wasmtime_wasi::preview2::bindings::wasi::clocks::wall_clock;
 use wasmtime_wasi::preview2::bindings::wasi::filesystem::types as filesystem;
 use wasmtime_wasi::preview2::command::{add_to_linker, Command};
 use wasmtime_wasi::preview2::{
-    self, DirPerms, FilePerms, HostMonotonicClock, HostWallClock, Table, WasiCtx, WasiCtxBuilder,
-    WasiView,
+    self, DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
 struct CommandCtx {
-    table: Table,
+    table: ResourceTable,
     wasi: WasiCtx,
 }
 
 impl WasiView for CommandCtx {
-    fn table(&self) -> &Table {
+    fn table(&self) -> &ResourceTable {
         &self.table
     }
-    fn table_mut(&mut self) -> &mut Table {
+    fn table_mut(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
     fn ctx(&self) -> &WasiCtx {
@@ -82,7 +81,7 @@ async fn api_time() -> Result<()> {
         }
     }
 
-    let table = Table::new();
+    let table = ResourceTable::new();
     let wasi = WasiCtxBuilder::new()
         .monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) })
         .wall_clock(FakeWallClock)
@@ -104,7 +103,7 @@ async fn api_read_only() -> Result<()> {
     std::fs::File::create(dir.path().join("bar.txt"))?.write_all(b"And stood awhile in thought")?;
     std::fs::create_dir(dir.path().join("sub"))?;
 
-    let table = Table::new();
+    let table = ResourceTable::new();
     let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?;
     let wasi = WasiCtxBuilder::new()
         .preopened_dir(open_dir, DirPerms::READ, FilePerms::READ, "/")
@@ -155,7 +154,7 @@ wasmtime::component::bindgen!({
 
 #[test_log::test(tokio::test)]
 async fn api_reactor() -> Result<()> {
-    let table = Table::new();
+    let table = ResourceTable::new();
     let wasi = WasiCtxBuilder::new().env("GOOD_DOG", "gussie").build();
 
     let mut config = Config::new();
diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs
index be7359e92..f47997e0e 100644
--- a/crates/wasi/tests/all/async_.rs
+++ b/crates/wasi/tests/all/async_.rs
@@ -351,6 +351,10 @@ async fn preview2_stream_pollable_traps() {
         .unwrap_err();
     assert_eq!(
         format!("{}", e.source().expect("trap source")),
-        "entry still has children"
+        "resource has children"
     )
 }
+#[test_log::test(tokio::test(flavor = "multi_thread"))]
+async fn preview2_adapter_badfd() {
+    run(PREVIEW2_ADAPTER_BADFD_COMPONENT, false).await.unwrap()
+}
diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs
index 1af5503f0..03cf8031b 100644
--- a/crates/wasi/tests/all/main.rs
+++ b/crates/wasi/tests/all/main.rs
@@ -2,17 +2,17 @@ use anyhow::Result;
 use cap_std::ambient_authority;
 use tempfile::TempDir;
 use wasmtime::{
-    component::{Component, Linker},
+    component::{Component, Linker, ResourceTable},
     Config, Engine, Store,
 };
 use wasmtime_wasi::preview2::{
     pipe::MemoryOutputPipe,
     preview1::{WasiPreview1Adapter, WasiPreview1View},
-    DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView,
+    DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
 struct Ctx {
-    table: Table,
+    table: ResourceTable,
     wasi: WasiCtx,
     stdout: MemoryOutputPipe,
     stderr: MemoryOutputPipe,
@@ -20,10 +20,10 @@ struct Ctx {
 }
 
 impl WasiView for Ctx {
-    fn table(&self) -> &Table {
+    fn table(&self) -> &ResourceTable {
         &self.table
     }
-    fn table_mut(&mut self) -> &mut Table {
+    fn table_mut(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
     fn ctx(&self) -> &WasiCtx {
@@ -75,7 +75,7 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>
     }
 
     let ctx = Ctx {
-        table: Table::new(),
+        table: ResourceTable::new(),
         wasi: builder.build(),
         stderr,
         stdout,
diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs
index 61d4cf69b..ce0c2e161 100644
--- a/crates/wasi/tests/all/sync.rs
+++ b/crates/wasi/tests/all/sync.rs
@@ -290,6 +290,10 @@ fn preview2_stream_pollable_traps() {
     let e = run(PREVIEW2_STREAM_POLLABLE_TRAPS_COMPONENT, false).unwrap_err();
     assert_eq!(
         format!("{}", e.source().expect("trap source")),
-        "entry still has children"
+        "resource has children"
     )
 }
+#[test_log::test]
+fn preview2_adapter_badfd() {
+    run(PREVIEW2_ADAPTER_BADFD_COMPONENT, false).unwrap()
+}
diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit
index 68dfaeaf3..f3cc6151b 100644
--- a/crates/wasi/wit/test.wit
+++ b/crates/wasi/wit/test.wit
@@ -2,7 +2,7 @@ package wasmtime:wasi;
 
 // only used as part of `test-programs`
 world test-reactor {
-  include wasi:cli/reactor@0.2.0-rc-2023-11-10;
+  include wasi:cli/imports@0.2.0-rc-2023-12-05;
 
   export add-strings: func(s: list<string>) -> u32;
   export get-strings: func() -> list<string>;
@@ -16,7 +16,7 @@ world test-reactor {
 }
 
 world test-command {
-  include wasi:cli/reactor@0.2.0-rc-2023-11-10;
-  import wasi:http/types@0.2.0-rc-2023-11-10;
-  import wasi:http/outgoing-handler@0.2.0-rc-2023-11-10;
+  include wasi:cli/imports@0.2.0-rc-2023-12-05;
+  import wasi:http/types@0.2.0-rc-2023-12-05;
+  import wasi:http/outgoing-handler@0.2.0-rc-2023-12-05;
 }
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 03783844b..8a7d08027 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1498,4 +1498,52 @@ mod test_programs {
         ])?;
         Ok(())
     }
+
+    #[test]
+    fn cli_no_tcp() -> Result<()> {
+        let output = super::run_wasmtime_for_output(
+            &[
+                "-Wcomponent-model",
+                // Turn on network but turn off TCP
+                "-Sinherit-network,tcp=no",
+                CLI_NO_TCP_COMPONENT,
+            ],
+            None,
+        )?;
+        println!("{}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
+
+    #[test]
+    fn cli_no_udp() -> Result<()> {
+        let output = super::run_wasmtime_for_output(
+            &[
+                "-Wcomponent-model",
+                // Turn on network but turn off UDP
+                "-Sinherit-network,udp=no",
+                CLI_NO_UDP_COMPONENT,
+            ],
+            None,
+        )?;
+        println!("{}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
+
+    #[test]
+    fn cli_no_ip_name_lookup() -> Result<()> {
+        let output = super::run_wasmtime_for_output(
+            &[
+                "-Wcomponent-model",
+                // Turn on network but ensure name lookup is disabled
+                "-Sinherit-network,allow-ip-name-lookup=no",
+                CLI_NO_IP_NAME_LOOKUP_COMPONENT,
+            ],
+            None,
+        )?;
+        println!("{}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
 }
diff --git a/tests/all/cli_tests/component-basic.wat b/tests/all/cli_tests/component-basic.wat
index 431fb2b92..6e009cab9 100644
--- a/tests/all/cli_tests/component-basic.wat
+++ b/tests/all/cli_tests/component-basic.wat
@@ -7,6 +7,6 @@
   (func $run (result (result))
     (canon lift (core func $i "run")))
 
-  (instance (export (interface "wasi:cli/run@0.2.0-rc-2023-11-10"))
+  (instance (export (interface "wasi:cli/run@0.2.0-rc-2023-12-05"))
     (export "run" (func $run)))
 )
diff --git a/winch/src/filetests.rs b/winch/src/filetests.rs
index 6917736c9..423f8d836 100644
--- a/winch/src/filetests.rs
+++ b/winch/src/filetests.rs
@@ -7,7 +7,7 @@ use clap::Parser;
 pub struct Options {
     /// Passes extra arguments to `cargo test --package winch-filetests`. For example, to run a single
     /// test, use `-- --test-threads 1 --test single_test_name`.
-    #[clap(last = true, value_parser)]
+    #[arg(last = true, value_parser)]
     cargo_test_args: Vec<String>,
 }