Repository: bytecodealliance/wasmtime
Pull request #7644 (2023-12-06 04:21:07)

Additional `extend` opt patterns
It already had the homogeneous chaining cases

https://github.com/bytecodealliance/wasmtime/blob/5c8bce70a11657ad6740f56a852f64d147f93494/cranelift/codegen/src/opts/extends.isle#L1-L5

This PR adds heterogeneous chaining cases, where possible.

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

cc @cfallin, @fitzgen

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

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

* cfallin: isle
* fitzgen: isle

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

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

Patch:
diff --git a/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/opts/extends.isle b/cranelift/codegen/src/opts/extends.isle
index a1a4dfda0..2f104b5a5 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))))
@@ -28,6 +36,15 @@
              (iconst _ (u64_from_imm64 0))))
       (iconst ty (imm64 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 _ (u64_from_imm64 0))))
+      (eq ty x (iconst ty (imm64 0))))
+(rule (simplify (ne _ (sextend _ x@(value_type ty)) (iconst _ (u64_from_imm64 0))))
+      (ne ty x (iconst ty (imm64 0))))
+(rule (simplify (icmp _ cc (sextend _ x@(value_type ty)) (iconst _ (u64_from_imm64 0))))
+      (if (signed_cond_code cc))
+      (icmp ty cc x (iconst ty (imm64 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.
 (rule (simplify (ireduce ty (sextend _ x @ (value_type ty)))) x)
diff --git a/cranelift/codegen/src/opts/spaceship.isle b/cranelift/codegen/src/opts/spaceship.isle
new file mode 100644
index 000000000..809274317
--- /dev/null
+++ b/cranelift/codegen/src/opts/spaceship.isle
@@ -0,0 +1,237 @@
+;; 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 ty neg_1)
+                           (ne rty x y)))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+(rule (simplify (select ty (ult rty x y)
+                           (iconst ty neg_1)
+                           (uextend ty (ne rty x y))))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (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 ty neg_1)
+                           (ugt rty x y)))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (sextend_from_i8 ty (spaceship_u rty x y)))
+(rule (simplify (select ty (ult rty x y)
+                           (iconst ty neg_1)
+                           (uextend ty (ugt rty x y))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (ult rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 1)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (ule rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 1)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (ugt rty x y)
+                                      (iconst ty (u64_from_imm64 1))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (uge rty x y)
+                                      (iconst ty (u64_from_imm64 1))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (ult rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 0)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (ne rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 0)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (eq rty x y)
+                                      (iconst ty (u64_from_imm64 0))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (uge rty x y)
+                                      (iconst ty (u64_from_imm64 0))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty neg_1)
+                           (ne rty x y)))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (spaceship_s rty x y))
+(rule (simplify (select ty (slt rty x y)
+                           (iconst ty neg_1)
+                           (uextend ty (ne rty x y))))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (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 ty neg_1)
+                           (sgt rty x y)))
+      (if-let -1 (i64_sextend_imm64 ty neg_1))
+      (spaceship_s rty x y))
+(rule (simplify (select ty (slt rty x y)
+                           (iconst ty neg_1)
+                           (uextend ty (sgt rty x y))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (slt rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 1)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (sle rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 1)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (sgt rty x y)
+                                      (iconst ty (u64_from_imm64 1))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 0))
+                           (select ty (sge rty x y)
+                                      (iconst ty (u64_from_imm64 1))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (slt rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 0)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (ne rty x y)
+                                      (iconst ty neg_1)
+                                      (iconst ty (u64_from_imm64 0)))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (eq rty x y)
+                                      (iconst ty (u64_from_imm64 0))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 ty (u64_from_imm64 1))
+                           (select ty (sge rty x y)
+                                      (iconst ty (u64_from_imm64 0))
+                                      (iconst ty neg_1))))
+      (if-let -1 (i64_sextend_imm64 ty neg_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 _ (u64_from_imm64 0))))
+      (eq ty x y))
+(rule (simplify (eq _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 0))))
+      (eq ty x y))
+;; (a <=> b) != 0 --> a != b
+(rule (simplify (ne _ (spaceship_s ty x y) (iconst _ (u64_from_imm64 0))))
+      (ne ty x y))
+(rule (simplify (ne _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 0))))
+      (ne ty x y))
+
+;; (a <=> b) < 0 --> a < b
+(rule (simplify (slt _ (spaceship_s ty x y) (iconst _ (u64_from_imm64 0))))
+      (slt ty x y))
+(rule (simplify (slt _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 0))))
+      (ult ty x y))
+;; (a <=> b) <= 0 --> a <= b
+(rule (simplify (sle _ (spaceship_s ty x y) (iconst _ (u64_from_imm64 0))))
+      (sle ty x y))
+(rule (simplify (sle _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 0))))
+      (ule ty x y))
+;; (a <=> b) > 0 --> a > b
+(rule (simplify (sgt _ (spaceship_s ty x y) (iconst _ (u64_from_imm64 0))))
+      (sgt ty x y))
+(rule (simplify (sgt _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 0))))
+      (ugt ty x y))
+;; (a <=> b) >= 0 --> a >= b
+(rule (simplify (sge _ (spaceship_s ty x y) (iconst _ (u64_from_imm64 0))))
+      (sge ty x y))
+(rule (simplify (sge _ (spaceship_u ty x y) (iconst _ (u64_from_imm64 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_opt.isle b/cranelift/codegen/src/prelude_opt.isle
index 921c7c65b..55dc75f0b 100644
--- a/cranelift/codegen/src/prelude_opt.isle
+++ b/cranelift/codegen/src/prelude_opt.isle
@@ -28,6 +28,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.
diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs
index 9fa5d83b3..928069b5e 100644
--- a/crates/runtime/src/instance/allocator/pooling.rs
+++ b/crates/runtime/src/instance/allocator/pooling.rs
@@ -13,50 +13,10 @@
 //! └──────┴──────┴──────┴──────┴──────┘
 //! ```
 //!
-//! Note that these slots are a useful abstraction but not exactly how this is
-//! mapped to memory in fact. Each new instance _does_ get associated with a
-//! slot number (see uses of `index` and [`SlotId`] in this module) but the
-//! parts of the instances are stored in separate pools: memories in the
-//! [`MemoryPool`], tables in the [`TablePool`], etc. What ties these various
-//! parts together is the slot number generated by an [`IndexAllocator`] .
-//!
-//! The [`MemoryPool`] protects Wasmtime from out-of-bounds memory accesses by
-//! inserting inaccessible guard regions between memory slots. The
-//! [`MemoryPool`] documentation has a more in-depth chart but one can think of
-//! memories being laid out like the following:
-//!
-//! ```text
-//! ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
-//! │Guard│Mem 0│Guard│Mem 1│Guard│Mem 2│.....│Guard│
-//! └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
-//! ```
-//!
-//! To complicate matters, each instance can have multiple memories, multiple
-//! tables, etc. You might think these would be stored consecutively in their
-//! respective pools (for instance `n`, table 0 is at table pool slot `n + 0`
-//! and table 1 is at `n + 1`), but for memories this is not the case. With
-//! protection keys enabled, memories do not need interleaved guard regions
-//! because the protection key will signal a fault if the wrong memory is
-//! accessed. Instead, the pooling allocator "stripes" the memories with
-//! different protection keys.
-//!
-//! This concept, dubbed [ColorGuard] in the original paper, relies on careful
-//! calculation of the memory sizes to prevent any "overlapping access": there
-//! are limited protection keys available (15) so the next memory using the same
-//! key must be at least as far away as the guard region we would insert
-//! otherwise. This ends up looking like the following, where a store for
-//! instance 0 (`I0`) "stripes" two memories (`M0` and `M1`) with the same
-//! protection key 1 and far enough apart to signal an OOB access:
-//!
-//! ```text
-//! ┌─────┬─────┬─────┬─────┬────────────────┬─────┬─────┬─────┐
-//! │.....│I0:M1│.....│.....│.<enough slots>.│I0:M2│.....│.....│
-//! ├─────┼─────┼─────┼─────┼────────────────┼─────┼─────┼─────┤
-//! │.....│key 1│key 2│key 3│..<more keys>...│key 1│key 2│.....│
-//! └─────┴─────┴─────┴─────┴────────────────┴─────┴─────┴─────┘
-//! ```
-//!
-//! [ColorGuard]: https://plas2022.github.io/files/pdf/SegueColorGuard.pdf
+//! Each slot has a "slot ID"--an index into the pool. Slot IDs are handed out
+//! by the [`index_allocator`] module. Note that each kind of pool-allocated
+//! item is stored in its own separate pool: [`memory_pool`], [`table_pool`],
+//! [`stack_pool`]. See those modules for more details.
 
 mod index_allocator;
 mod memory_pool;
diff --git a/crates/runtime/src/instance/allocator/pooling/memory_pool.rs b/crates/runtime/src/instance/allocator/pooling/memory_pool.rs
index 67aefe106..9658e43d8 100644
--- a/crates/runtime/src/instance/allocator/pooling/memory_pool.rs
+++ b/crates/runtime/src/instance/allocator/pooling/memory_pool.rs
@@ -1,11 +1,9 @@
 //! Implements a memory pool using a single allocated memory slab.
 //!
-//! The pooling instance allocator maps memory in advance and allocates
-//! instances, memories, tables, and stacks from a pool of available resources.
-//! Using the pooling instance allocator can speed up module instantiation when
-//! modules can be constrained based on configurable limits
-//! ([`InstanceLimits`]). Each new instance is stored in a "slot"; as instances
-//! are allocated and freed, these slots are either filled or emptied:
+//! The pooling instance allocator maps one large slab of memory in advance and
+//! allocates WebAssembly memories from this slab--a [`MemoryPool`]. Each
+//! WebAssembly memory is allocated in its own slot (see uses of `index` and
+//! [`SlotId`] in this module):
 //!
 //! ```text
 //! ┌──────┬──────┬──────┬──────┬──────┐
@@ -13,17 +11,12 @@
 //! └──────┴──────┴──────┴──────┴──────┘
 //! ```
 //!
-//! Note that these slots are a useful abstraction but not exactly how this is
-//! mapped to memory in fact. Each new instance _does_ get associated with a
-//! slot number (see uses of `index` and [`SlotId`] in this module) but the
-//! parts of the instances are stored in separate pools: memories in the
-//! [`MemoryPool`], tables in the [`TablePool`], etc. What ties these various
-//! parts together is the slot number generated by an [`IndexAllocator`] .
-//!
-//! The [`MemoryPool`] protects Wasmtime from out-of-bounds memory accesses by
-//! inserting inaccessible guard regions between memory slots. The
-//! [`MemoryPool`] documentation has a more in-depth chart but one can think of
-//! memories being laid out like the following:
+//! Diving deeper, we note that a [`MemoryPool`] protects Wasmtime from
+//! out-of-bounds memory accesses by inserting inaccessible guard regions
+//! between memory slots. These guard regions are configured to raise a signal
+//! if they are accessed--a WebAssembly out-of-bounds (OOB) memory access. The
+//! [`MemoryPool`] documentation has a more detailed chart but one can think of
+//! memory slots being laid out like the following:
 //!
 //! ```text
 //! ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
@@ -31,22 +24,21 @@
 //! └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
 //! ```
 //!
-//! To complicate matters, each instance can have multiple memories, multiple
-//! tables, etc. You might think these would be stored consecutively in their
-//! respective pools (for instance `n`, table 0 is at table pool slot `n + 0`
-//! and table 1 is at `n + 1`), but for memories this is not the case. With
-//! protection keys enabled, memories do not need interleaved guard regions
-//! because the protection key will signal a fault if the wrong memory is
-//! accessed. Instead, the pooling allocator "stripes" the memories with
-//! different protection keys.
+//! But we can be more efficient about guard regions: with memory protection
+//! keys (MPK) enabled, the interleaved guard regions can be smaller. If we
+//! surround a memory with memories from other instances and each instance is
+//! protected by different protection keys, the guard region can be smaller AND
+//! the pool will still raise a signal on an OOB access. This complicates how we
+//! lay out memory slots: we must store memories from the same instance in the
+//! same "stripe". Each stripe is protected by a different protection key.
 //!
 //! This concept, dubbed [ColorGuard] in the original paper, relies on careful
-//! calculation of the memory sizes to prevent any "overlapping access": there
-//! are limited protection keys available (15) so the next memory using the same
-//! key must be at least as far away as the guard region we would insert
-//! otherwise. This ends up looking like the following, where a store for
-//! instance 0 (`I0`) "stripes" two memories (`M0` and `M1`) with the same
-//! protection key 1 and far enough apart to signal an OOB access:
+//! calculation of the memory sizes to prevent any "overlapping access" (see
+//! [`calculate`]): there are limited protection keys available (15) so the next
+//! memory using the same key must be at least as far away as the guard region
+//! we would insert otherwise. This ends up looking like the following, where a
+//! store for instance 0 (`I0`) "stripes" two memories (`M0` and `M1`) with the
+//! same protection key 1 and far enough apart to signal an OOB access:
 //!
 //! ```text
 //! ┌─────┬─────┬─────┬─────┬────────────────┬─────┬─────┬─────┐
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/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/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index 22fcb171c..b19f8c948 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__")]
@@ -2641,7 +2641,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 +2666,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/mod.rs b/crates/wasi/src/preview2/mod.rs
index 481b588e7..d4eb0e409 100644
--- a/crates/wasi/src/preview2/mod.rs
+++ b/crates/wasi/src/preview2/mod.rs
@@ -95,7 +95,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/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/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++) {


Test patch:
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/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/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/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/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)))
 )