Repository: bytecodealliance/wasmtime
Pull request #7263 (2023-10-17 00:41:15)

PCC: switch "max" facts to "range" facts with two-sided ranges.
This is needed for soundness when verifying accesses to memtype fields: it's not enough to know that we're accessing an offset in `0` up to `field_offset` inclusive, we need to know the access is actually to `field_offset`.

The simplest change that validates this turned out to be the most general one: making ranges two-sided rather than one-sided. The transform is *mostly* mechanical, but a few new tests verify that ranges are updated on both sides, and some fail-tests verify that "fuzzily imprecise" pointers to struct fields fail to validate.

<!--
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/Cargo.lock b/Cargo.lock
index f3956ba07..7b45f8915 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -650,7 +650,7 @@ dependencies = [
  "target-lexicon",
  "thiserror",
  "toml",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wat",
 ]
 
@@ -829,7 +829,7 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime-types",
  "wat",
 ]
@@ -2680,7 +2680,7 @@ version = "0.0.0"
 dependencies = [
  "cargo_metadata",
  "heck",
- "wit-component",
+ "wit-component 0.15.0",
 ]
 
 [[package]]
@@ -2977,7 +2977,7 @@ name = "verify-component-adapter"
 version = "15.0.0"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wat",
 ]
 
@@ -3075,7 +3075,7 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder",
+ "wasm-encoder 0.35.0",
  "wit-bindgen",
 ]
 
@@ -3159,47 +3159,57 @@ dependencies = [
  "leb128",
 ]
 
+[[package]]
+name = "wasm-encoder"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca90ba1b5b0a70d3d49473c5579951f3bddc78d47b59256d2f9d4922b150aca"
+dependencies = [
+ "leb128",
+]
+
 [[package]]
 name = "wasm-metadata"
-version = "0.10.6"
+version = "0.10.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "577508d8a45bc54ad97efe77c95ba57bb10e7e5c5bac9c31295ce88b8045cd7d"
+checksum = "14abc161bfda5b519aa229758b68f2a52b45a12b993808665c857d1a9a00223c"
 dependencies = [
  "anyhow",
  "indexmap 2.0.0",
  "serde",
+ "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.35"
+version = "0.2.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f22ff62504d0e55a4e4c32cdf4e65f9c925ba0bc9904e141394eb2ad2b8f319d"
+checksum = "7b1e04b0c049b0a0c42dd108a56c5c92500076747363d3bf1e83e7f0f8b4dfe4"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.12.18"
+version = "0.12.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7499466e905b4e8d0cc1720c1bfddf1e5040f6fa42efd4f43edd1b88dbe9ce9b"
+checksum = "fef779c243bbf04d9f03333c2cb50b98047c6dcc2a1db0cc7d0691e4135064b4"
 dependencies = [
  "arbitrary",
  "flagset",
  "indexmap 2.0.0",
  "leb128",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
 ]
 
 [[package]]
@@ -3250,6 +3260,16 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "wasmparser"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e06c0641a4add879ba71ccb3a1e4278fd546f76f1eafb21d8f7b07733b547cd5"
+dependencies = [
+ "indexmap 2.0.0",
+ "semver",
+]
+
 [[package]]
 name = "wasmparser-nostd"
 version = "0.91.0"
@@ -3261,12 +3281,12 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.67"
+version = "0.2.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6615a5587149e753bf4b93f90fa3c3f41c88597a7a2da72879afcabeda9648f"
+checksum = "e74458a9bc5cc9c7108abfa0fe4dc88d5abf1f3baf194df3264985f17d559b5e"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.115.0",
 ]
 
 [[package]]
@@ -3294,8 +3314,8 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "wasi-cap-std-sync",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
  "wasmtime-cache",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -3413,8 +3433,8 @@ dependencies = [
  "test-programs-artifacts",
  "tokio",
  "walkdir",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3428,7 +3448,7 @@ dependencies = [
  "wasmtime-wasi-nn",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 65.0.2",
+ "wast 66.0.2",
  "wat",
  "windows-sys",
 ]
@@ -3459,7 +3479,7 @@ dependencies = [
  "wasmtime",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
@@ -3483,7 +3503,7 @@ dependencies = [
  "object",
  "target-lexicon",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
@@ -3519,8 +3539,8 @@ dependencies = [
  "serde_derive",
  "target-lexicon",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.35.0",
+ "wasmparser 0.115.0",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -3535,7 +3555,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger 0.10.0",
  "libfuzzer-sys",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3592,7 +3612,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3612,12 +3632,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder",
+ "wasm-encoder 0.35.0",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -3688,7 +3708,7 @@ dependencies = [
  "rand",
  "rustix 0.38.8",
  "sptr",
- "wasm-encoder",
+ "wasm-encoder 0.35.0",
  "wasmtime-asm-macros",
  "wasmtime-environ",
  "wasmtime-fiber",
@@ -3706,7 +3726,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.115.0",
 ]
 
 [[package]]
@@ -3812,7 +3832,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 65.0.2",
+ "wast 66.0.2",
 ]
 
 [[package]]
@@ -3824,7 +3844,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "winch-codegen",
@@ -3837,7 +3857,7 @@ dependencies = [
  "anyhow",
  "heck",
  "indexmap 2.0.0",
- "wit-parser",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
@@ -3855,23 +3875,23 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "65.0.2"
+version = "66.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55a88724cf8c2c0ebbf32c8e8f4ac0d6aa7ba6d73a1cfd94b254aa8f894317e"
+checksum = "93cb43b0ac6dd156f2c375735ccfd72b012a7c0a6e6d09503499b8d3cb6e6072"
 dependencies = [
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder",
+ "wasm-encoder 0.35.0",
 ]
 
 [[package]]
 name = "wat"
-version = "1.0.74"
+version = "1.0.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d83e1a8d86d008adc7bafa5cf4332d448699a08fcf2a715a71fbb75e2c5ca188"
+checksum = "e367582095d2903caeeea9acbb140e1db9c7677001efa4347c3687fd34fe7072"
 dependencies = [
- "wast 65.0.2",
+ "wast 66.0.2",
 ]
 
 [[package]]
@@ -3996,7 +4016,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime-environ",
 ]
 
@@ -4041,7 +4061,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser",
+ "wasmparser 0.115.0",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4142,8 +4162,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f0371c47784e7559efb422f74473e395b49f7101725584e2673657e0b4fc104"
 dependencies = [
  "anyhow",
- "wit-component",
- "wit-parser",
+ "wit-component 0.14.4",
+ "wit-parser 0.11.3",
 ]
 
 [[package]]
@@ -4157,7 +4177,7 @@ dependencies = [
  "wasm-metadata",
  "wit-bindgen-core",
  "wit-bindgen-rust-lib",
- "wit-component",
+ "wit-component 0.14.4",
 ]
 
 [[package]]
@@ -4182,7 +4202,7 @@ dependencies = [
  "wit-bindgen-core",
  "wit-bindgen-rust",
  "wit-bindgen-rust-lib",
- "wit-component",
+ "wit-component 0.14.4",
 ]
 
 [[package]]
@@ -4197,10 +4217,29 @@ dependencies = [
  "log",
  "serde",
  "serde_json",
- "wasm-encoder",
+ "wasm-encoder 0.33.2",
  "wasm-metadata",
- "wasmparser",
- "wit-parser",
+ "wasmparser 0.113.2",
+ "wit-parser 0.11.3",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1bc644bb4205a2ee74035e5d35958777575fa4c6dd38ce7226c7680be2861c1"
+dependencies = [
+ "anyhow",
+ "bitflags 2.3.3",
+ "indexmap 2.0.0",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder 0.35.0",
+ "wasm-metadata",
+ "wasmparser 0.115.0",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
@@ -4221,6 +4260,23 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "wit-parser"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d08c9557c65c428ac18a3cce80bf2527dbec24ab06639642c7db73f2c7613f93"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.0.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+]
+
 [[package]]
 name = "witx"
 version = "0.9.1"
diff --git a/Cargo.toml b/Cargo.toml
index 7c5aa7d9b..f759a4ed4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -210,15 +210,15 @@ is-terminal = "0.4.0"
 wit-bindgen = { version = "0.12.0", default-features = false }
 
 # wasm-tools family:
-wasmparser = "0.113.2"
-wat = "1.0.74"
-wast = "65.0.2"
-wasmprinter = "0.2.67"
-wasm-encoder = "0.33.2"
-wasm-smith = "0.12.18"
-wasm-mutate = "0.2.35"
-wit-parser = "0.11.3"
-wit-component = "0.14.4"
+wasmparser = "0.115.0"
+wat = "1.0.77"
+wast = "66.0.2"
+wasmprinter = "0.2.70"
+wasm-encoder = "0.35.0"
+wasm-smith = "0.12.21"
+wasm-mutate = "0.2.38"
+wit-parser = "0.12.0"
+wit-component = "0.15.0"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
diff --git a/cranelift/codegen/src/ir/pcc.rs b/cranelift/codegen/src/ir/pcc.rs
index 739f51f96..7502610a5 100644
--- a/cranelift/codegen/src/ir/pcc.rs
+++ b/cranelift/codegen/src/ir/pcc.rs
@@ -128,19 +128,25 @@ pub enum PccError {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
 pub enum Fact {
-    /// A bitslice of a value (up to a bitwidth) is less than or equal
-    /// to a given maximum value.
+    /// A bitslice of a value (up to a bitwidth) is within the given
+    /// integer range.
     ///
     /// The slicing behavior is needed because this fact can describe
     /// both an SSA `Value`, whose entire value is well-defined, and a
     /// `VReg` in VCode, whose bits beyond the type stored in that
     /// register are don't-care (undefined).
-    ValueMax {
+    Range {
         /// The bitwidth of bits we care about, from the LSB upward.
         bit_width: u16,
+        /// The minimum value that the bitslice can take
+        /// (inclusive). The range is unsigned: the specified bits of
+        /// the actual value will be greater than or equal to this
+        /// value, as evaluated by an unsigned integer comparison.
+        min: u64,
         /// The maximum value that the bitslice can take
-        /// (inclusive). The range is unsigned: the bits of the value
-        /// will be within the range `0..=max`.
+        /// (inclusive). The range is unsigned: the specified bits of
+        /// the actual value will be less than or equal to this value,
+        /// as evaluated by an unsigned integer comparison.
         max: u64,
     },
 
@@ -148,16 +154,26 @@ pub enum Fact {
     Mem {
         /// The memory type.
         ty: ir::MemoryType,
-        /// The offset into the memory type.
-        offset: i64,
+        /// The minimum offset into the memory type, inclusive.
+        min_offset: u64,
+        /// The maximum offset into the memory type, inclusive.
+        max_offset: u64,
     },
 }
 
 impl fmt::Display for Fact {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            Fact::ValueMax { bit_width, max } => write!(f, "max({}, {:#x})", bit_width, max),
-            Fact::Mem { ty, offset } => write!(f, "mem({}, {:#x})", ty, offset),
+            Fact::Range {
+                bit_width,
+                min,
+                max,
+            } => write!(f, "range({}, {:#x}, {:#x})", bit_width, min, max),
+            Fact::Mem {
+                ty,
+                min_offset,
+                max_offset,
+            } => write!(f, "mem({}, {:#x}, {:#x})", ty, min_offset, max_offset),
         }
     }
 }
@@ -166,20 +182,24 @@ impl Fact {
     /// Try to infer a minimal fact for a value of the given IR type.
     pub fn infer_from_type(ty: ir::Type) -> Option<&'static Self> {
         static FACTS: [Fact; 4] = [
-            Fact::ValueMax {
+            Fact::Range {
                 bit_width: 8,
+                min: 0,
                 max: u8::MAX as u64,
             },
-            Fact::ValueMax {
+            Fact::Range {
                 bit_width: 16,
+                min: 0,
                 max: u16::MAX as u64,
             },
-            Fact::ValueMax {
+            Fact::Range {
                 bit_width: 32,
+                min: 0,
                 max: u32::MAX as u64,
             },
-            Fact::ValueMax {
+            Fact::Range {
                 bit_width: 64,
+                min: 0,
                 max: u64::MAX,
             },
         ];
@@ -231,24 +251,25 @@ impl<'a> FactContext<'a> {
             (l, r) if l == r => true,
 
             (
-                Fact::ValueMax {
+                Fact::Range {
                     bit_width: bw_lhs,
+                    min: min_lhs,
                     max: max_lhs,
                 },
-                Fact::ValueMax {
+                Fact::Range {
                     bit_width: bw_rhs,
+                    min: min_rhs,
                     max: max_rhs,
                 },
             ) => {
                 // If the bitwidths we're claiming facts about are the
-                // same, and if the value is less than or equal to
-                // `max_lhs`, and if `max_rhs` is less than `max_lhs`,
-                // then it is certainly less than or equal to
-                // `max_rhs`.
+                // same, and if the right-hand-side range is larger
+                // than the left-hand-side range, than the LHS
+                // subsumes the RHS.
                 //
                 // In other words, we can always expand the claimed
                 // possible value range.
-                bw_lhs == bw_rhs && max_lhs <= max_rhs
+                bw_lhs == bw_rhs && max_lhs <= max_rhs && min_lhs >= min_rhs
             }
 
             _ => false,
@@ -275,39 +296,58 @@ impl<'a> FactContext<'a> {
     pub fn add(&self, lhs: &Fact, rhs: &Fact, add_width: u16) -> Option<Fact> {
         match (lhs, rhs) {
             (
-                Fact::ValueMax {
+                Fact::Range {
                     bit_width: bw_lhs,
-                    max: lhs,
+                    min: min_lhs,
+                    max: max_lhs,
                 },
-                Fact::ValueMax {
+                Fact::Range {
                     bit_width: bw_rhs,
-                    max: rhs,
+                    min: min_rhs,
+                    max: max_rhs,
                 },
             ) if bw_lhs == bw_rhs && add_width >= *bw_lhs => {
-                let computed_max = lhs.checked_add(*rhs)?;
+                let computed_min = min_lhs.checked_add(*min_rhs)?;
+                let computed_max = max_lhs.checked_add(*max_rhs)?;
                 let computed_max = std::cmp::min(max_value_for_width(add_width), computed_max);
-                Some(Fact::ValueMax {
+                Some(Fact::Range {
                     bit_width: *bw_lhs,
+                    min: computed_min,
                     max: computed_max,
                 })
             }
 
             (
-                Fact::ValueMax {
+                Fact::Range {
                     bit_width: bw_max,
+                    min,
                     max,
                 },
-                Fact::Mem { ty, offset },
+                Fact::Mem {
+                    ty,
+                    min_offset,
+                    max_offset,
+                },
             )
             | (
-                Fact::Mem { ty, offset },
-                Fact::ValueMax {
+                Fact::Mem {
+                    ty,
+                    min_offset,
+                    max_offset,
+                },
+                Fact::Range {
                     bit_width: bw_max,
+                    min,
                     max,
                 },
             ) if *bw_max >= self.pointer_width && add_width >= *bw_max => {
-                let offset = offset.checked_add(i64::try_from(*max).ok()?)?;
-                Some(Fact::Mem { ty: *ty, offset })
+                let min_offset = min_offset.checked_add(*min)?;
+                let max_offset = max_offset.checked_add(*max)?;
+                Some(Fact::Mem {
+                    ty: *ty,
+                    min_offset,
+                    max_offset,
+                })
             }
 
             _ => None,
@@ -322,14 +362,20 @@ impl<'a> FactContext<'a> {
             // bit_width and from_bits are exactly contiguous, then we
             // have defined values in 0..to_bits (and because this is
             // a zero-extend, the max value is the same).
-            Fact::ValueMax { bit_width, max } if *bit_width == from_width => Some(Fact::ValueMax {
+            Fact::Range {
+                bit_width,
+                min,
+                max,
+            } if *bit_width == from_width => Some(Fact::Range {
                 bit_width: to_width,
+                min: *min,
                 max: *max,
             }),
             // Otherwise, we can at least claim that the value is
             // within the range of `to_width`.
-            Fact::ValueMax { .. } => Some(Fact::ValueMax {
+            Fact::Range { .. } => Some(Fact::Range {
                 bit_width: to_width,
+                min: 0,
                 max: max_value_for_width(to_width),
             }),
             _ => None,
@@ -342,9 +388,13 @@ impl<'a> FactContext<'a> {
             // If we have a defined value in bits 0..bit_width, and
             // the MSB w.r.t. `from_width` is *not* set, then we can
             // do the same as `uextend`.
-            Fact::ValueMax { bit_width, max }
-                if *bit_width == from_width && (*max & (1 << (*bit_width - 1)) == 0) =>
-            {
+            Fact::Range {
+                bit_width,
+                // We can ignore `min`: it is always <= max in
+                // unsigned terms, and we check max's LSB below.
+                min: _,
+                max,
+            } if *bit_width == from_width && (*max & (1 << (*bit_width - 1)) == 0) => {
                 self.uextend(fact, from_width, to_width)
             }
             _ => None,
@@ -353,23 +403,20 @@ impl<'a> FactContext<'a> {
 
     /// Scales a value with a fact by a known constant.
     pub fn scale(&self, fact: &Fact, width: u16, factor: u32) -> Option<Fact> {
-        // The minimal (loosest) fact we can claim: the value will be
-        // within the range implied by its bitwidth.
-        let minimal_fact = Fact::ValueMax {
-            bit_width: width,
-            max: max_value_for_width(width),
-        };
         match fact {
-            Fact::ValueMax { bit_width, max } if *bit_width == width => {
-                let max = match max.checked_mul(u64::from(factor)) {
-                    Some(max) => max,
-                    None => return Some(minimal_fact),
-                };
+            Fact::Range {
+                bit_width,
+                min,
+                max,
+            } if *bit_width == width => {
+                let min = min.checked_mul(u64::from(factor))?;
+                let max = max.checked_mul(u64::from(factor))?;
                 if *bit_width < 64 && max > max_value_for_width(width) {
-                    return Some(minimal_fact);
+                    return None;
                 }
-                Some(Fact::ValueMax {
+                Some(Fact::Range {
                     bit_width: *bit_width,
+                    min,
                     max,
                 })
             }
@@ -388,36 +435,38 @@ impl<'a> FactContext<'a> {
 
     /// Offsets a value with a fact by a known amount.
     pub fn offset(&self, fact: &Fact, width: u16, offset: i64) -> Option<Fact> {
-        match fact {
-            Fact::ValueMax { bit_width, max } if *bit_width == width => {
-                // If we eventually support two-sided ranges, we can
-                // represent (0..n) + m -> ((0+m)..(n+m)). However,
-                // right now, all ranges start with zero, so any
-                // negative offset could underflow, and removes all
-                // claims of constrained range.
-                let offset = u64::try_from(offset).ok()?;
-
-                let max = match max.checked_add(offset) {
-                    Some(max) => max,
-                    None => {
-                        return Some(Fact::ValueMax {
-                            bit_width: width,
-                            max: max_value_for_width(width),
-                        })
-                    }
-                };
+        // Any negative offset could underflow, and removes
+        // all claims of constrained range, so for now we only
+        // support positive offsets.
+        let offset = u64::try_from(offset).ok()?;
 
-                Some(Fact::ValueMax {
+        match fact {
+            Fact::Range {
+                bit_width,
+                min,
+                max,
+            } if *bit_width == width => {
+                let min = min.checked_add(offset)?;
+                let max = max.checked_add(offset)?;
+
+                Some(Fact::Range {
                     bit_width: *bit_width,
+                    min,
                     max,
                 })
             }
             Fact::Mem {
                 ty,
-                offset: mem_offset,
+                min_offset: mem_min_offset,
+                max_offset: mem_max_offset,
             } => {
-                let offset = mem_offset.checked_sub(offset)?;
-                Some(Fact::Mem { ty: *ty, offset })
+                let min_offset = mem_min_offset.checked_sub(offset)?;
+                let max_offset = mem_max_offset.checked_sub(offset)?;
+                Some(Fact::Mem {
+                    ty: *ty,
+                    min_offset,
+                    max_offset,
+                })
             }
             _ => None,
         }
@@ -427,15 +476,18 @@ impl<'a> FactContext<'a> {
     /// a memory access of the given size, is valid.
     ///
     /// If valid, returns the memory type and offset into that type
-    /// that this address accesses.
-    fn check_address(&self, fact: &Fact, size: u32) -> PccResult<(ir::MemoryType, i64)> {
+    /// that this address accesses, if known, or `None` if the range
+    /// doesn't constrain the access to exactly one location.
+    fn check_address(&self, fact: &Fact, size: u32) -> PccResult<Option<(ir::MemoryType, u64)>> {
         match fact {
-            Fact::Mem { ty, offset } => {
-                let end_offset: i64 = offset
-                    .checked_add(i64::from(size))
+            Fact::Mem {
+                ty,
+                min_offset,
+                max_offset,
+            } => {
+                let end_offset: u64 = max_offset
+                    .checked_add(u64::from(size))
                     .ok_or(PccError::Overflow)?;
-                let end_offset: u64 =
-                    u64::try_from(end_offset).map_err(|_| PccError::OutOfBounds)?;
                 match &self.function.memory_types[*ty] {
                     ir::MemoryTypeData::Struct { size, .. }
                     | ir::MemoryTypeData::Memory { size } => {
@@ -443,7 +495,12 @@ impl<'a> FactContext<'a> {
                     }
                     ir::MemoryTypeData::Empty => bail!(OutOfBounds),
                 }
-                Ok((*ty, *offset))
+                let specific_ty_and_offset = if min_offset == max_offset {
+                    Some((*ty, *min_offset))
+                } else {
+                    None
+                };
+                Ok(specific_ty_and_offset)
             }
             _ => bail!(OutOfBounds),
         }
@@ -456,9 +513,10 @@ impl<'a> FactContext<'a> {
         fact: &Fact,
         access_ty: ir::Type,
     ) -> PccResult<Option<&'b ir::MemoryTypeField>> {
-        let (ty, offset) = self.check_address(fact, access_ty.bytes())?;
-        let offset =
-            u64::try_from(offset).expect("valid access address cannot have a negative offset");
+        let (ty, offset) = match self.check_address(fact, access_ty.bytes())? {
+            Some((ty, offset)) => (ty, offset),
+            None => return Ok(None),
+        };
 
         if let ir::MemoryTypeData::Struct { fields, .. } = &self.function.memory_types[ty] {
             let field = fields
diff --git a/cranelift/codegen/src/isa/aarch64/pcc.rs b/cranelift/codegen/src/isa/aarch64/pcc.rs
index dbc24e195..83a039ec6 100644
--- a/cranelift/codegen/src/isa/aarch64/pcc.rs
+++ b/cranelift/codegen/src/isa/aarch64/pcc.rs
@@ -147,8 +147,9 @@ pub(crate) fn check(ctx: &FactContext, vcode: &VCode<Inst>, inst: &Inst) -> PccR
             imm12,
         } => check_output(&ctx, vcode, rd.to_reg(), || {
             let rn = get_fact_or_default(vcode, *rn)?;
-            let imm_fact = Fact::ValueMax {
+            let imm_fact = Fact::Range {
                 bit_width: size.bits().into(),
+                min: imm12.value(),
                 max: imm12.value(),
             };
             fail_if_missing(ctx.add(&rn, &imm_fact, size.bits().into()))
@@ -218,8 +219,9 @@ pub(crate) fn check(ctx: &FactContext, vcode: &VCode<Inst>, inst: &Inst) -> PccR
             // Any ALU op can validate a max-value fact where the
             // value is the maximum for its bit-width.
             check_output(&ctx, vcode, rd.to_reg(), || {
-                Ok(Fact::ValueMax {
+                Ok(Fact::Range {
                     bit_width: size.bits().into(),
+                    min: 0,
                     max: size.max_value(),
                 })
             })
diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle
index afcdad717..6da824e62 100644
--- a/cranelift/codegen/src/isa/riscv64/inst.isle
+++ b/cranelift/codegen/src/isa/riscv64/inst.isle
@@ -1666,12 +1666,41 @@
 (rule (rv_brev8 rs1)
   (alu_rr_funct12 (AluOPRRI.Brev8) rs1))
 
+;; `Zbs` Extension Instructions
+
+(decl rv_bclr (XReg XReg) XReg)
+(rule (rv_bclr rs1 rs2)
+  (alu_rrr (AluOPRRR.Bclr) rs1 rs2))
+
+(decl rv_bclri (XReg Imm12) XReg)
+(rule (rv_bclri rs1 imm)
+  (alu_rr_imm12 (AluOPRRI.Bclri) rs1 imm))
+
+(decl rv_bext (XReg XReg) XReg)
+(rule (rv_bext rs1 rs2)
+  (alu_rrr (AluOPRRR.Bext) rs1 rs2))
+
+(decl rv_bexti (XReg Imm12) XReg)
+(rule (rv_bexti rs1 imm)
+  (alu_rr_imm12 (AluOPRRI.Bexti) rs1 imm))
+
+(decl rv_binv (XReg XReg) XReg)
+(rule (rv_binv rs1 rs2)
+  (alu_rrr (AluOPRRR.Binv) rs1 rs2))
+
+(decl rv_binvi (XReg Imm12) XReg)
+(rule (rv_binvi rs1 imm)
+  (alu_rr_imm12 (AluOPRRI.Binvi) rs1 imm))
+
+(decl rv_bset (XReg XReg) XReg)
+(rule (rv_bset rs1 rs2)
+  (alu_rrr (AluOPRRR.Bset) rs1 rs2))
+
 ;; Helper for emitting the `bseti` ("Single-Bit Set Immediate") instruction.
 (decl rv_bseti (XReg Imm12) XReg)
 (rule (rv_bseti rs1 imm)
   (alu_rr_imm12 (AluOPRRI.Bseti) rs1 imm))
 
-
 ;; `Zbkb` Extension Instructions
 
 ;; Helper for emitting the `pack` ("Pack low halves of registers") instruction.
diff --git a/cranelift/codegen/src/isa/riscv64/lower.isle b/cranelift/codegen/src/isa/riscv64/lower.isle
index 2fbeeed37..021a3503d 100644
--- a/cranelift/codegen/src/isa/riscv64/lower.isle
+++ b/cranelift/codegen/src/isa/riscv64/lower.isle
@@ -556,7 +556,7 @@
 (rule 1 (lower (has_type (fits_in_16 ty) (sdiv x y @ (iconst imm))))
   (if-let $true (has_m))
   (if (safe_divisor_from_imm64 ty imm))
-  (rv_divw (sext x) y))
+  (rv_divw (sext x) (sext y)))
 
 (rule 2 (lower (has_type $I32 (sdiv x y)))
   (if-let $true (has_m))
@@ -604,7 +604,7 @@
 (rule 1 (lower (has_type (fits_in_16 ty) (urem x y @ (iconst imm))))
   (if-let $true (has_m))
   (if (safe_divisor_from_imm64 ty imm))
-  (rv_remuw (zext x) y))
+  (rv_remuw (zext x) (zext y)))
 
 (rule 2 (lower (has_type $I32 (urem x y)))
   (if-let $true (has_m))
@@ -633,7 +633,7 @@
 (rule 1 (lower (has_type (fits_in_16 ty) (srem x y @ (iconst imm))))
   (if-let $true (has_m))
   (if (safe_divisor_from_imm64 ty imm))
-  (rv_remw (sext x) y))
+  (rv_remw (sext x) (sext y)))
 
 (rule 2 (lower (has_type $I32 (srem x y)))
   (if-let $true (has_m))
@@ -715,6 +715,74 @@
   (if-let x_imm (replicated_imm5 x))
   (rv_vand_vi y x_imm (unmasked) ty))
 
+;; `bclr{,i}` specializations from `zbs`
+
+(rule 13 (lower (has_type (fits_in_32 ty) (band x (bnot (ishl (i64_from_iconst 1) y)))))
+  (if-let $true (has_zbs))
+  (rv_bclr x (rv_andi y (imm12_const (u8_sub (ty_bits ty) 1)))))
+(rule 14 (lower (has_type (fits_in_32 ty) (band (bnot (ishl (i64_from_iconst 1) y)) x)))
+  (if-let $true (has_zbs))
+  (rv_bclr x (rv_andi y (imm12_const (u8_sub (ty_bits ty) 1)))))
+
+(rule 15 (lower (has_type $I64 (band x (bnot (ishl (i64_from_iconst 1) y)))))
+  (if-let $true (has_zbs))
+  (rv_bclr x y))
+(rule 16 (lower (has_type $I64 (band (bnot (ishl (i64_from_iconst 1) y)) x)))
+  (if-let $true (has_zbs))
+  (rv_bclr x y))
+
+(rule 17 (lower (has_type (fits_in_64 ty) (band x (u64_from_iconst n))))
+  (if-let $true (has_zbs))
+  (if-let imm (bclr_imm ty n))
+  (rv_bclri x imm))
+(rule 18 (lower (has_type (fits_in_64 ty) (band (u64_from_iconst n) x)))
+  (if-let $true (has_zbs))
+  (if-let imm (bclr_imm ty n))
+  (rv_bclri x imm))
+
+(decl pure partial bclr_imm (Type u64) Imm12)
+(extern constructor bclr_imm bclr_imm)
+
+;; `bext{,i}` specializations from `zbs`
+
+(rule 19 (lower (has_type $I32 (band (ushr x y) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bext x (rv_andi y (imm12_const 31))))
+(rule 19 (lower (has_type $I32 (band (sshr x y) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bext x (rv_andi y (imm12_const 31))))
+(rule 19 (lower (has_type $I32 (band (u64_from_iconst 1) (ushr x y))))
+  (if-let $true (has_zbs))
+  (rv_bext x (rv_andi y (imm12_const 31))))
+(rule 19 (lower (has_type $I32 (band (u64_from_iconst 1) (sshr x y))))
+  (if-let $true (has_zbs))
+  (rv_bext x (rv_andi y (imm12_const 31))))
+
+(rule 19 (lower (has_type $I64 (band (ushr x y) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bext x y))
+(rule 19 (lower (has_type $I64 (band (sshr x y) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bext x y))
+(rule 19 (lower (has_type $I64 (band (u64_from_iconst 1) (ushr x y))))
+  (if-let $true (has_zbs))
+  (rv_bext x y))
+(rule 19 (lower (has_type $I64 (band (u64_from_iconst 1) (sshr x y))))
+  (if-let $true (has_zbs))
+  (rv_bext x y))
+
+(rule 20 (lower (has_type $I32 (band (ushr x (imm12_from_value y)) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bexti x (imm12_and y 31)))
+(rule 20 (lower (has_type $I32 (band (sshr x (imm12_from_value y)) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bexti x (imm12_and y 31)))
+(rule 20 (lower (has_type $I64 (band (ushr x (imm12_from_value y)) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bexti x (imm12_and y 63)))
+(rule 20 (lower (has_type $I64 (band (sshr x (imm12_from_value y)) (u64_from_iconst 1))))
+  (if-let $true (has_zbs))
+  (rv_bexti x (imm12_and y 63)))
 
 ;;;; Rules for `or` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (rule 0 (lower (has_type (ty_int ty) (bor x y)))
@@ -773,6 +841,33 @@
   (if-let x_imm (replicated_imm5 x))
   (rv_vor_vi y x_imm (unmasked) ty))
 
+;; `bset{,i}` specializations from `zbs`
+
+(rule 13 (lower (has_type $I32 (bor x (ishl (i64_from_iconst 1) y))))
+  (if-let $true (has_zbs))
+  (rv_bset x (rv_andi y (imm12_const 31))))
+(rule 14 (lower (has_type $I32 (bor (ishl (i64_from_iconst 1) y) x)))
+  (if-let $true (has_zbs))
+  (rv_bset x (rv_andi y (imm12_const 31))))
+
+(rule 13 (lower (has_type $I64 (bor x (ishl (i64_from_iconst 1) y))))
+  (if-let $true (has_zbs))
+  (rv_bset x y))
+(rule 14 (lower (has_type $I64 (bor (ishl (i64_from_iconst 1) y) x)))
+  (if-let $true (has_zbs))
+  (rv_bset x y))
+
+(rule 15 (lower (has_type (fits_in_64 _) (bor x (u64_from_iconst n))))
+  (if-let $true (has_zbs))
+  (if-let imm (bseti_imm n))
+  (rv_bseti x imm))
+(rule 16 (lower (has_type (fits_in_64 _) (bor (u64_from_iconst n) x)))
+  (if-let $true (has_zbs))
+  (if-let imm (bseti_imm n))
+  (rv_bseti x imm))
+
+(decl pure partial bseti_imm (u64) Imm12)
+(extern constructor bseti_imm bseti_imm)
 
 ;;;; Rules for `xor` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (rule 0 (lower (has_type (fits_in_64 (ty_int ty)) (bxor x y)))
@@ -810,6 +905,33 @@
   (if-let x_imm (replicated_imm5 x))
   (rv_vxor_vi y x_imm (unmasked) ty))
 
+;; `binv{,i}` specializations from `zbs`
+
+(rule 13 (lower (has_type $I32 (bxor x (ishl (i64_from_iconst 1) y))))
+  (if-let $true (has_zbs))
+  (rv_binv x (rv_andi y (imm12_const 31))))
+(rule 14 (lower (has_type $I32 (bxor (ishl (i64_from_iconst 1) y) x)))
+  (if-let $true (has_zbs))
+  (rv_binv x (rv_andi y (imm12_const 31))))
+
+(rule 13 (lower (has_type $I64 (bxor x (ishl (i64_from_iconst 1) y))))
+  (if-let $true (has_zbs))
+  (rv_binv x y))
+(rule 14 (lower (has_type $I64 (bxor (ishl (i64_from_iconst 1) y) x)))
+  (if-let $true (has_zbs))
+  (rv_binv x y))
+
+(rule 15 (lower (has_type (fits_in_64 _) (bxor x (u64_from_iconst n))))
+  (if-let $true (has_zbs))
+  (if-let imm (binvi_imm n))
+  (rv_binvi x imm))
+(rule 16 (lower (has_type (fits_in_64 _) (bxor (u64_from_iconst n) x)))
+  (if-let $true (has_zbs))
+  (if-let imm (binvi_imm n))
+  (rv_binvi x imm))
+
+(decl pure partial binvi_imm (u64) Imm12)
+(extern constructor binvi_imm binvi_imm)
 
 ;;;; Rules for `bnot` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (rule 0 (lower (has_type (ty_scalar ty) (bnot x)))
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index d4d449d0e..232e35e08 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -521,6 +521,26 @@ impl generated_code::Context for RV64IsleContext<'_, '_, MInst, Riscv64Backend>
     fn vec_alu_rr_dst_type(&mut self, op: &VecAluOpRR) -> Type {
         MInst::canonical_type_for_rc(op.dst_regclass())
     }
+
+    fn bclr_imm(&mut self, ty: Type, i: u64) -> Option<Imm12> {
+        // Only consider those bits in the immediate which are up to the width
+        // of `ty`.
+        let neg = !i & (u64::MAX >> (64 - ty.bits()));
+        if neg.count_ones() != 1 {
+            return None;
+        }
+        Imm12::maybe_from_u64(neg.trailing_zeros().into())
+    }
+
+    fn binvi_imm(&mut self, i: u64) -> Option<Imm12> {
+        if i.count_ones() != 1 {
+            return None;
+        }
+        Imm12::maybe_from_u64(i.trailing_zeros().into())
+    }
+    fn bseti_imm(&mut self, i: u64) -> Option<Imm12> {
+        self.binvi_imm(i)
+    }
 }
 
 /// The main entry point for lowering with ISLE.
diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs
index eabb7dff4..16baa3588 100644
--- a/cranelift/reader/src/parser.rs
+++ b/cranelift/reader/src/parser.rs
@@ -2147,39 +2147,50 @@ impl<'a> Parser<'a> {
 
     // Parse a "fact" for proof-carrying code, attached to a value.
     //
-    // fact ::= "max" "(" bit-width "," max-value ")"
-    //        | "mem" "(" memory-type "," mt-offset ")"
+    // fact ::= "range" "(" bit-width "," min-value "," max-value ")"
+    //        | "mem" "(" memory-type "," mt-offset "," mt-offset ")"
     // bit-width ::= uimm64
+    // min-value ::= uimm64
     // max-value ::= uimm64
     // valid-range ::= uimm64
-    // mt-offset ::= imm64
+    // mt-offset ::= uimm64
     fn parse_fact(&mut self) -> ParseResult<Fact> {
         match self.token() {
-            Some(Token::Identifier("max")) => {
+            Some(Token::Identifier("range")) => {
                 self.consume();
-                self.match_token(Token::LPar, "`max` fact needs an opening `(`")?;
+                self.match_token(Token::LPar, "`range` fact needs an opening `(`")?;
                 let bit_width: u64 = self
-                    .match_uimm64("expected a bit-width value for `max` fact")?
+                    .match_uimm64("expected a bit-width value for `range` fact")?
+                    .into();
+                self.match_token(Token::Comma, "expected a comma")?;
+                let min: u64 = self
+                    .match_uimm64("expected a min value for `range` fact")?
                     .into();
                 self.match_token(Token::Comma, "expected a comma")?;
                 let max: u64 = self
-                    .match_uimm64("expected a max value for `max` fact")?
+                    .match_uimm64("expected a max value for `range` fact")?
                     .into();
-                self.match_token(Token::RPar, "`max` fact needs a closing `)`")?;
+                self.match_token(Token::RPar, "`range` fact needs a closing `)`")?;
                 let bit_width_max = match bit_width {
                     x if x > 64 => {
-                        return Err(self.error("bitwidth must be <= 64 bits on a `max` fact"));
+                        return Err(self.error("bitwidth must be <= 64 bits on a `range` fact"));
                     }
                     64 => u64::MAX,
                     x => (1u64 << x) - 1,
                 };
+                if min > max {
+                    return Err(self.error(
+                        "min value must be less than or equal to max value on a `range` fact",
+                    ));
+                }
                 if max > bit_width_max {
                     return Err(
-                        self.error("max value is out of range for bitwidth on a `max` fact")
+                        self.error("max value is out of range for bitwidth on a `range` fact")
                     );
                 }
-                Ok(Fact::ValueMax {
+                Ok(Fact::Range {
                     bit_width: u16::try_from(bit_width).unwrap(),
+                    min: min.into(),
                     max: max.into(),
                 })
             }
@@ -2191,13 +2202,24 @@ impl<'a> Parser<'a> {
                     Token::Comma,
                     "expected a comma after memory type in `mem` fact",
                 )?;
-                let offset: i64 = self
-                    .match_imm64("expected an imm64 pointer offset for `mem` fact")?
+                let min_offset: u64 = self
+                    .match_uimm64("expected a uimm64 minimum pointer offset for `mem` fact")?
+                    .into();
+                self.match_token(
+                    Token::Comma,
+                    "expected a comma after memory type in `mem` fact",
+                )?;
+                let max_offset: u64 = self
+                    .match_uimm64("expected a uimm64 maximum pointer offset for `mem` fact")?
                     .into();
                 self.match_token(Token::RPar, "expected a `)`")?;
-                Ok(Fact::Mem { ty, offset })
+                Ok(Fact::Mem {
+                    ty,
+                    min_offset,
+                    max_offset,
+                })
             }
-            _ => Err(self.error("expected a `max` or `mem` fact")),
+            _ => Err(self.error("expected a `range` or `mem` fact")),
         }
     }
 
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index 3b9aa6cad..c3b88e5e6 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -230,6 +230,10 @@ impl Config {
         ret.wasm_simd(true);
         ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
 
+        ret.wasm_relaxed_simd(false);
+        ret.wasm_threads(false);
+        ret.wasm_multi_memory(false);
+
         // This is on-by-default in `wasmparser` since it's a stage 4+ proposal
         // but it's not implemented in Wasmtime yet so disable it.
         ret.features.tail_call = false;
diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs
index 32d4cc2d0..defb6dd08 100644
--- a/crates/wast/src/wast.rs
+++ b/crates/wast/src/wast.rs
@@ -465,6 +465,10 @@ impl<T> WastContext<T> {
                 }
             }
             AssertException { .. } => bail!("unimplemented assert_exception"),
+
+            Thread(_) => bail!("unimplemented `thread`"),
+
+            Wait { .. } => bail!("unimplemented `wait`"),
         }
 
         Ok(())
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 70303719b..8b74c5ad6 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -3386,3 +3386,9 @@ criteria = "safe-to-deploy"
 user-id = 6825 # Dan Gohman (sunfishcode)
 start = "2019-08-20"
 end = "2024-07-14"
+
+[[trusted.wit-parser]]
+criteria = "safe-to-deploy"
+user-id = 696 # Nick Fitzgerald (fitzgen)
+start = "2023-10-12"
+end = "2024-10-17"
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 27558affd..727934b53 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -831,6 +831,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-encoder]]
+version = "0.35.0"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-metadata]]
 version = "0.10.6"
 when = "2023-09-26"
@@ -838,6 +845,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-metadata]]
+version = "0.10.9"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-mutate]]
 version = "0.2.35"
 when = "2023-09-26"
@@ -845,6 +859,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-mutate]]
+version = "0.2.38"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-smith]]
 version = "0.12.18"
 when = "2023-09-26"
@@ -852,6 +873,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-smith]]
+version = "0.12.21"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmparser]]
 version = "0.113.2"
 when = "2023-09-26"
@@ -859,6 +887,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmparser]]
+version = "0.115.0"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmprinter]]
 version = "0.2.67"
 when = "2023-09-26"
@@ -866,6 +901,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmprinter]]
+version = "0.2.70"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmtime]]
 version = "13.0.0"
 when = "2023-09-20"
@@ -1017,6 +1059,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wast]]
+version = "66.0.2"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wat]]
 version = "1.0.74"
 when = "2023-09-26"
@@ -1024,6 +1073,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wat]]
+version = "1.0.77"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wiggle]]
 version = "13.0.0"
 when = "2023-09-20"
@@ -1174,6 +1230,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-component]]
+version = "0.15.0"
+when = "2023-10-14"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-parser]]
 version = "0.11.3"
 when = "2023-09-27"
@@ -1181,6 +1244,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-parser]]
+version = "0.12.0"
+when = "2023-10-12"
+user-id = 696
+user-login = "fitzgen"
+user-name = "Nick Fitzgerald"
+
 [[audits.embark-studios.wildcard-audits.spdx]]
 who = "Jake Shadle <opensource@embark-studios.com>"
 criteria = "safe-to-deploy"
diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs
index ddee15bcf..adddda012 100644
--- a/winch/codegen/src/isa/x64/asm.rs
+++ b/winch/codegen/src/isa/x64/asm.rs
@@ -1,7 +1,7 @@
 //! Assembler library implementation for x64.
 
 use crate::{
-    isa::reg::{Reg, RegClass},
+    isa::reg::Reg,
     masm::{CmpKind, DivKind, OperandSize, RemKind, RoundingMode, ShiftKind},
 };
 use cranelift_codegen::{
@@ -409,32 +409,13 @@ impl Assembler {
 
     /// "and" two registers.
     pub fn and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
-        match dst.class() {
-            RegClass::Int => {
-                self.emit(Inst::AluRmiR {
-                    size: size.into(),
-                    op: AluRmiROpcode::And,
-                    src1: dst.into(),
-                    src2: src.into(),
-                    dst: dst.into(),
-                });
-            }
-            RegClass::Float => {
-                let op = match size {
-                    OperandSize::S32 => SseOpcode::Andps,
-                    OperandSize::S64 => SseOpcode::Andpd,
-                    OperandSize::S128 => unreachable!(),
-                };
-
-                self.emit(Inst::XmmRmR {
-                    op,
-                    src1: dst.into(),
-                    src2: XmmMemAligned::from(Xmm::from(src)),
-                    dst: dst.into(),
-                });
-            }
-            RegClass::Vector => unreachable!(),
-        }
+        self.emit(Inst::AluRmiR {
+            size: size.into(),
+            op: AluRmiROpcode::And,
+            src1: dst.into(),
+            src2: src.into(),
+            dst: dst.into(),
+        });
     }
 
     pub fn and_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
@@ -448,6 +429,22 @@ impl Assembler {
         });
     }
 
+    /// "and" two float registers.
+    pub fn xmm_and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+        let op = match size {
+            OperandSize::S32 => SseOpcode::Andps,
+            OperandSize::S64 => SseOpcode::Andpd,
+            OperandSize::S128 => unreachable!(),
+        };
+
+        self.emit(Inst::XmmRmR {
+            op,
+            src1: dst.into(),
+            src2: XmmMemAligned::from(Xmm::from(src)),
+            dst: dst.into(),
+        });
+    }
+
     pub fn gpr_to_xmm(&mut self, src: Reg, dst: Reg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Movd,
@@ -487,32 +484,13 @@ impl Assembler {
 
     /// Logical exclusive or with registers.
     pub fn xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
-        match dst.class() {
-            RegClass::Int => {
-                self.emit(Inst::AluRmiR {
-                    size: size.into(),
-                    op: AluRmiROpcode::Xor,
-                    src1: dst.into(),
-                    src2: src.into(),
-                    dst: dst.into(),
-                });
-            }
-            RegClass::Float => {
-                let op = match size {
-                    OperandSize::S32 => SseOpcode::Xorps,
-                    OperandSize::S64 => SseOpcode::Xorpd,
-                    OperandSize::S128 => unreachable!(),
-                };
-
-                self.emit(Inst::XmmRmR {
-                    op,
-                    src1: dst.into(),
-                    src2: XmmMemAligned::from(Xmm::from(src)),
-                    dst: dst.into(),
-                });
-            }
-            RegClass::Vector => todo!(),
-        }
+        self.emit(Inst::AluRmiR {
+            size: size.into(),
+            op: AluRmiROpcode::Xor,
+            src1: dst.into(),
+            src2: src.into(),
+            dst: dst.into(),
+        });
     }
 
     pub fn xor_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
@@ -527,6 +505,22 @@ impl Assembler {
         });
     }
 
+    /// Logical exclusive or with float registers.
+    pub fn xmm_xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+        let op = match size {
+            OperandSize::S32 => SseOpcode::Xorps,
+            OperandSize::S64 => SseOpcode::Xorpd,
+            OperandSize::S128 => unreachable!(),
+        };
+
+        self.emit(Inst::XmmRmR {
+            op,
+            src1: dst.into(),
+            src2: XmmMemAligned::from(Xmm::from(src)),
+            dst: dst.into(),
+        });
+    }
+
     /// Shift with register and register.
     pub fn shift_rr(&mut self, src: Reg, dst: Reg, kind: ShiftKind, size: OperandSize) {
         self.emit(Inst::ShiftR {
@@ -823,7 +817,9 @@ impl Assembler {
         });
     }
 
-    pub fn rounds(&mut self, src: Reg, dst: Reg, mode: RoundingMode, size: OperandSize) {
+    /// Perform rounding operation on float register src and place results in
+    /// float register dst.
+    pub fn xmm_rounds_rr(&mut self, src: Reg, dst: Reg, mode: RoundingMode, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Roundss,
             OperandSize::S64 => SseOpcode::Roundsd,
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index e580253fd..8b1ddcad8 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -380,7 +380,7 @@ impl Masm for MacroAssembler {
         self.load_constant(&mask, scratch_gpr, size);
         let scratch_xmm = regs::scratch_xmm();
         self.asm.gpr_to_xmm(scratch_gpr, scratch_xmm, size);
-        self.asm.xor_rr(scratch_xmm, dst, size);
+        self.asm.xmm_xor_rr(scratch_xmm, dst, size);
     }
 
     fn float_abs(&mut self, dst: Reg, size: OperandSize) {
@@ -394,13 +394,13 @@ impl Masm for MacroAssembler {
         self.load_constant(&mask, scratch_gpr, size);
         let scratch_xmm = regs::scratch_xmm();
         self.asm.gpr_to_xmm(scratch_gpr, scratch_xmm, size);
-        self.asm.and_rr(scratch_xmm, dst, size);
+        self.asm.xmm_and_rr(scratch_xmm, dst, size);
     }
 
     fn float_round(&mut self, mode: RoundingMode, context: &mut CodeGenContext, size: OperandSize) {
         if self.flags.has_sse41() {
             let src = context.pop_to_reg(self, None);
-            self.asm.rounds(src.into(), src.into(), mode, size);
+            self.asm.xmm_rounds_rr(src.into(), src.into(), mode, size);
             context.stack.push(src.into());
         } else {
             FnCall::emit::<Self, Self::Ptr, _>(self, context, |context| {


Test patch:
diff --git a/cranelift/filetests/filetests/isa/riscv64/wasm/zbs.wat b/cranelift/filetests/filetests/isa/riscv64/wasm/zbs.wat
new file mode 100644
index 000000000..3d14406d5
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/riscv64/wasm/zbs.wat
@@ -0,0 +1,398 @@
+;;! target = "riscv64"
+;;! compile = true
+;;! settings = ["has_zbs", "opt_level=speed"]
+
+(module
+  (func (export "bclr32") (param i32 i32) (result i32)
+    (i32.and (local.get 0) (i32.xor (i32.shl (i32.const 1) (local.get 1)) (i32.const -1)))
+  )
+  (func (export "bclr64") (param i64 i64) (result i64)
+    (i64.and (i64.xor (i64.shl (i64.const 1) (local.get 1)) (i64.const -1)) (local.get 0))
+  )
+  (func (export "bclri32_4") (param i32) (result i32)
+    (i32.and (local.get 0) (i32.xor (i32.shl (i32.const 1) (i32.const 4)) (i32.const -1)))
+  )
+  (func (export "bclri32_20") (param i32) (result i32)
+    (i32.and (i32.xor (i32.shl (i32.const 1) (i32.const 20)) (i32.const -1)) (local.get 0))
+  )
+  (func (export "bclri64_4") (param i64) (result i64)
+    (i64.and (local.get 0) (i64.xor (i64.shl (i64.const 1) (i64.const 4)) (i64.const -1)))
+  )
+  (func (export "bclri64_52") (param i64) (result i64)
+    (i64.and (local.get 0) (i64.xor (i64.shl (i64.const 1) (i64.const 52)) (i64.const -1)))
+  )
+
+  (func (export "bext32_1") (param i32 i32) (result i32)
+    (i32.and (i32.shr_u (local.get 0) (local.get 1)) (i32.const 1))
+  )
+  (func (export "bext32_2") (param i32 i32) (result i32)
+    (i32.and (i32.shr_s (local.get 0) (local.get 1)) (i32.const 1))
+  )
+  (func (export "bext32_3") (param i32 i32) (result i32)
+    (i32.and (i32.const 1) (i32.shr_u (local.get 0) (local.get 1)))
+  )
+  (func (export "bext32_4") (param i32 i32) (result i32)
+    (i32.and (i32.const 1) (i32.shr_s (local.get 0) (local.get 1)))
+  )
+  (func (export "bext64_1") (param i64 i64) (result i64)
+    (i64.and (i64.shr_u (local.get 0) (local.get 1)) (i64.const 1))
+  )
+  (func (export "bext64_2") (param i64 i64) (result i64)
+    (i64.and (i64.shr_s (local.get 0) (local.get 1)) (i64.const 1))
+  )
+  (func (export "bext64_3") (param i64 i64) (result i64)
+    (i64.and (i64.const 1) (i64.shr_u (local.get 0) (local.get 1)))
+  )
+  (func (export "bext64_4") (param i64 i64) (result i64)
+    (i64.and (i64.const 1) (i64.shr_s (local.get 0) (local.get 1)))
+  )
+
+  (func (export "bexti32_1") (param i32) (result i32)
+    (i32.and (i32.shr_u (local.get 0) (i32.const 10)) (i32.const 1))
+  )
+  (func (export "bexti32_2") (param i32) (result i32)
+    (i32.and (i32.shr_s (local.get 0) (i32.const 20)) (i32.const 1))
+  )
+  (func (export "bexti32_3") (param i32) (result i32)
+    (i32.and (i32.shr_u (i32.const 1) (local.get 0) (i32.const 30)))
+  )
+  (func (export "bexti32_4") (param i32) (result i32)
+    (i32.and (i32.shr_s (i32.const 1) (local.get 0) (i32.const 40)))
+  )
+  (func (export "bexti64_1") (param i64) (result i64)
+    (i64.and (i64.shr_u (local.get 0) (i64.const 10)) (i64.const 1))
+  )
+  (func (export "bexti64_2") (param i64) (result i64)
+    (i64.and (i64.shr_s (local.get 0) (i64.const 20)) (i64.const 1))
+  )
+  (func (export "bexti64_3") (param i64) (result i64)
+    (i64.and (i64.shr_u (i64.const 1) (local.get 0) (i64.const 30)))
+  )
+  (func (export "bexti64_4") (param i64) (result i64)
+    (i64.and (i64.shr_s (i64.const 1) (local.get 0) (i64.const 40)))
+  )
+
+  (func (export "binv32_1") (param i32 i32) (result i32)
+    (i32.xor (local.get 0) (i32.shl (i32.const 1) (local.get 1)))
+  )
+  (func (export "binv32_2") (param i32 i32) (result i32)
+    (i32.xor (i32.shl (i32.const 1) (local.get 1)) (local.get 0))
+  )
+  (func (export "binv64_1") (param i64 i64) (result i64)
+    (i64.xor (local.get 0) (i64.shl (i64.const 1) (local.get 1)))
+  )
+  (func (export "binv64_2") (param i64 i64) (result i64)
+    (i64.xor (i64.shl (i64.const 1) (local.get 1)) (local.get 0))
+  )
+  (func (export "binvi32_1") (param i32) (result i32)
+    (i32.xor (local.get 0) (i32.shl (i32.const 1) (i32.const 10)))
+  )
+  (func (export "binvi32_2") (param i32) (result i32)
+    (i32.xor (i32.shl (i32.const 1) (i32.const 20)) (local.get 0))
+  )
+  (func (export "binvi64_1") (param i64) (result i64)
+    (i64.xor (local.get 0) (i64.shl (i64.const 1) (i64.const 30)))
+  )
+  (func (export "binvi64_2") (param i64) (result i64)
+    (i64.xor (i64.shl (i64.const 1) (i64.const 40)) (local.get 0))
+  )
+
+  (func (export "bset32_1") (param i32 i32) (result i32)
+    (i32.or (local.get 0) (i32.shl (i32.const 1) (local.get 1)))
+  )
+  (func (export "bset32_2") (param i32 i32) (result i32)
+    (i32.or (i32.shl (i32.const 1) (local.get 1)) (local.get 0))
+  )
+  (func (export "bset64_1") (param i64 i64) (result i64)
+    (i64.or (local.get 0) (i64.shl (i64.const 1) (local.get 1)))
+  )
+  (func (export "bset64_2") (param i64 i64) (result i64)
+    (i64.or (i64.shl (i64.const 1) (local.get 1)) (local.get 0))
+  )
+  (func (export "bseti32_1") (param i32) (result i32)
+    (i32.or (local.get 0) (i32.shl (i32.const 1) (i32.const 10)))
+  )
+  (func (export "bseti32_2") (param i32) (result i32)
+    (i32.or (i32.shl (i32.const 1) (i32.const 20)) (local.get 0))
+  )
+  (func (export "bseti64_1") (param i64) (result i64)
+    (i64.or (local.get 0) (i64.shl (i64.const 1) (i64.const 30)))
+  )
+  (func (export "bseti64_2") (param i64) (result i64)
+    (i64.or (i64.shl (i64.const 1) (i64.const 40)) (local.get 0))
+  )
+)
+;; function u0:0:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bclr a0,a0,a5
+;;   ret
+;;
+;; function u0:1:
+;; block0:
+;;   j label1
+;; block1:
+;;   bclr a0,a0,a1
+;;   ret
+;;
+;; function u0:2:
+;; block0:
+;;   j label1
+;; block1:
+;;   bclri a0,a0,4
+;;   ret
+;;
+;; function u0:3:
+;; block0:
+;;   j label1
+;; block1:
+;;   bclri a0,a0,20
+;;   ret
+;;
+;; function u0:4:
+;; block0:
+;;   j label1
+;; block1:
+;;   bclri a0,a0,4
+;;   ret
+;;
+;; function u0:5:
+;; block0:
+;;   j label1
+;; block1:
+;;   bclri a0,a0,52
+;;   ret
+;;
+;; function u0:6:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bext a0,a0,a5
+;;   ret
+;;
+;; function u0:7:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bext a0,a0,a5
+;;   ret
+;;
+;; function u0:8:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bext a0,a0,a5
+;;   ret
+;;
+;; function u0:9:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bext a0,a0,a5
+;;   ret
+;;
+;; function u0:10:
+;; block0:
+;;   j label1
+;; block1:
+;;   bext a0,a0,a1
+;;   ret
+;;
+;; function u0:11:
+;; block0:
+;;   j label1
+;; block1:
+;;   bext a0,a0,a1
+;;   ret
+;;
+;; function u0:12:
+;; block0:
+;;   j label1
+;; block1:
+;;   bext a0,a0,a1
+;;   ret
+;;
+;; function u0:13:
+;; block0:
+;;   j label1
+;; block1:
+;;   bext a0,a0,a1
+;;   ret
+;;
+;; function u0:14:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,10
+;;   ret
+;;
+;; function u0:15:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,20
+;;   ret
+;;
+;; function u0:16:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,30
+;;   ret
+;;
+;; function u0:17:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,8
+;;   ret
+;;
+;; function u0:18:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,10
+;;   ret
+;;
+;; function u0:19:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,20
+;;   ret
+;;
+;; function u0:20:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,30
+;;   ret
+;;
+;; function u0:21:
+;; block0:
+;;   j label1
+;; block1:
+;;   bexti a0,a0,40
+;;   ret
+;;
+;; function u0:22:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   binv a0,a0,a5
+;;   ret
+;;
+;; function u0:23:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   binv a0,a0,a5
+;;   ret
+;;
+;; function u0:24:
+;; block0:
+;;   j label1
+;; block1:
+;;   binv a0,a0,a1
+;;   ret
+;;
+;; function u0:25:
+;; block0:
+;;   j label1
+;; block1:
+;;   binv a0,a0,a1
+;;   ret
+;;
+;; function u0:26:
+;; block0:
+;;   j label1
+;; block1:
+;;   binvi a0,a0,10
+;;   ret
+;;
+;; function u0:27:
+;; block0:
+;;   j label1
+;; block1:
+;;   binvi a0,a0,20
+;;   ret
+;;
+;; function u0:28:
+;; block0:
+;;   j label1
+;; block1:
+;;   binvi a0,a0,30
+;;   ret
+;;
+;; function u0:29:
+;; block0:
+;;   j label1
+;; block1:
+;;   binvi a0,a0,40
+;;   ret
+;;
+;; function u0:30:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bset a0,a0,a5
+;;   ret
+;;
+;; function u0:31:
+;; block0:
+;;   j label1
+;; block1:
+;;   andi a5,a1,31
+;;   bset a0,a0,a5
+;;   ret
+;;
+;; function u0:32:
+;; block0:
+;;   j label1
+;; block1:
+;;   bset a0,a0,a1
+;;   ret
+;;
+;; function u0:33:
+;; block0:
+;;   j label1
+;; block1:
+;;   bset a0,a0,a1
+;;   ret
+;;
+;; function u0:34:
+;; block0:
+;;   j label1
+;; block1:
+;;   bseti a0,a0,10
+;;   ret
+;;
+;; function u0:35:
+;; block0:
+;;   j label1
+;; block1:
+;;   bseti a0,a0,20
+;;   ret
+;;
+;; function u0:36:
+;; block0:
+;;   j label1
+;; block1:
+;;   bseti a0,a0,30
+;;   ret
+;;
+;; function u0:37:
+;; block0:
+;;   j label1
+;; block1:
+;;   bseti a0,a0,40
+;;   ret
diff --git a/cranelift/filetests/filetests/pcc/fail/add.clif b/cranelift/filetests/filetests/pcc/fail/add.clif
index e32ed55d6..a40879f3d 100644
--- a/cranelift/filetests/filetests/pcc/fail/add.clif
+++ b/cranelift/filetests/filetests/pcc/fail/add.clif
@@ -3,48 +3,48 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32, i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(32, 0x80): i32):
-    v2 ! max(32, 0x17f) = iadd.i32 v0, v1
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(32, 0, 0x80): i32):
+    v2 ! range(32, 0, 0x17f) = iadd.i32 v0, v1
     return v2
 }
 
 function %f1(i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0x100) = iadd.i32 v0, v1
+block0(v0 ! range(32, 0, 0x100): i32):
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0x100) = iadd.i32 v0, v1
     return v2
 }
 
 function %f3(i32) -> i64 {
 block0(v0: i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0xffff_fffe) = iadd.i32 v0, v1
-    v3 ! max(64, 0xffff_fffe) = uextend.i64 v2
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0xffff_fffe) = iadd.i32 v0, v1
+    v3 ! range(64, 0, 0xffff_fffe) = uextend.i64 v2
     return v3
 }
 
 function %f3(i32) -> i64 {
 block0(v0: i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0xffff_ffff) = iadd.i32 v0, v1
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v2
-    v4 ! max(64, 0x1) = iconst.i64 1
-    v5 ! max(64, 0xffff_ffff) = iadd.i64 v3, v4
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0xffff_ffff) = iadd.i32 v0, v1
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v2
+    v4 ! range(64, 0, 0x1) = iconst.i64 1
+    v5 ! range(64, 0, 0xffff_ffff) = iadd.i64 v3, v4
     return v5
 }
 
 ;; check merged ops:
 function %f4(i32, i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(32, 0x200): i32):
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(32, 0, 0x200): i32):
   v2 = iconst.i32 2
-  v3 ! max(32, 0x400) = ishl.i32 v0, v2
-  v4 ! max(32, 0x5ff) = iadd.i32 v1, v3
+  v3 ! range(32, 0, 0x400) = ishl.i32 v0, v2
+  v4 ! range(32, 0, 0x5ff) = iadd.i32 v1, v3
   return v4
 }
 
 function %f5(i32, i64) -> i64 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(64, 0x200): i64):
-  v2 ! max(64, 0x100) = uextend.i64 v0
-  v3 ! max(64, 0x2ff) = iadd.i64 v1, v2
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(64, 0, 0x200): i64):
+  v2 ! range(64, 0, 0x100) = uextend.i64 v0
+  v3 ! range(64, 0, 0x2ff) = iadd.i64 v1, v2
   return v3
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/blockparams.clif b/cranelift/filetests/filetests/pcc/fail/blockparams.clif
index 29863e06b..0e8386f83 100644
--- a/cranelift/filetests/filetests/pcc/fail/blockparams.clif
+++ b/cranelift/filetests/filetests/pcc/fail/blockparams.clif
@@ -3,17 +3,17 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i64, i32) -> i64 {
-block0(v0 ! max(64, 0x100): i64, v1: i32):
-    v2 ! max(64, 0x100) = iconst.i64 0x100
-    v3 ! max(64, 0x200) = iadd v0, v2
+block0(v0 ! range(64, 0, 0x100): i64, v1: i32):
+    v2 ! range(64, 0, 0x100) = iconst.i64 0x100
+    v3 ! range(64, 0, 0x200) = iadd v0, v2
     brif v1, block1(v0), block2(v3)
 
-block1(v4 ! max(64, 0xff): i64):  ;; shrink the range -- should be caught
+block1(v4 ! range(64, 0, 0xff): i64):  ;; shrink the range -- should be caught
     jump block3(v4)
 
-block2(v5 ! max(64, 0x1ff): i64):
+block2(v5 ! range(64, 0, 0x1ff): i64):
     jump block3(v5)
 
-block3(v6 ! max(64, 1): i64):
+block3(v6 ! range(64, 0, 1): i64):
     return v6
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/extend.clif b/cranelift/filetests/filetests/pcc/fail/extend.clif
index dab088874..a1c31ee5f 100644
--- a/cranelift/filetests/filetests/pcc/fail/extend.clif
+++ b/cranelift/filetests/filetests/pcc/fail/extend.clif
@@ -3,8 +3,8 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32) -> i64 {
-block0(v0 ! max(32, 0xffff_ffff): i32):
-  v1 ! max(64, 0xffff_0000) = uextend.i64 v0
+block0(v0 ! range(32, 0, 0xffff_ffff): i32):
+  v1 ! range(64, 0, 0xffff_0000) = uextend.i64 v0
   return v1
 }
 
@@ -14,7 +14,7 @@ block0(v0 ! max(32, 0xffff_ffff): i32):
 ;; possible. If the `i32` were taken through another 32-bit operation
 ;; and we asserted its 32-bit range at that point, it would work.
 function %f1(i32) -> i64 {
-block0(v0 ! max(16, 0xffff): i32):
-  v1 ! max(64, 0xffff_ffff) = uextend.i64 v0
+block0(v0 ! range(16, 0, 0xffff): i32):
+  v1 ! range(64, 0, 0xffff_ffff) = uextend.i64 v0
   return v1
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/load.clif b/cranelift/filetests/filetests/pcc/fail/load.clif
index a7b916add..5fa42311d 100644
--- a/cranelift/filetests/filetests/pcc/fail/load.clif
+++ b/cranelift/filetests/filetests/pcc/fail/load.clif
@@ -4,9 +4,9 @@ target aarch64
 
 function %f0(i64, i32) -> i64 {
     mt0 = memory 0x1000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0x1000): i32):
-    v2 ! max(64, 0x100) = uextend.i64 v1
-    v3 ! mem(mt0, 0x100) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0x1000): i32):
+    v2 ! range(64, 0, 0x100) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0x100) = iadd.i64 v0, v2
     v4 = load.i64 checked v3
     return v4
 }
@@ -14,9 +14,9 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0x1000): i32):
 ;; Insufficient guard region: the 8-byte load could go off the end.
 function %f1(i64, i32) -> i64 {
     mt0 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
-    v2 ! max(64, 0xffff_ffff) = uextend.i64 v1
-    v3 ! mem(mt0, 0xffff_ffff) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xffff_ffff): i32):
+    v2 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0xffff_ffff) = iadd.i64 v0, v2
     v4 = load.i64 checked v3
     return v4
 }
@@ -24,9 +24,9 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
 ;; RegRegExtend mode on aarch64.
 function %f2(i64, i32) -> i8 {
     mt0 = memory 0x1000
-block0(v0 ! mem(mt0, 0x1000): i64, v1 ! max(32, 0x1000): i32):
-    v2 ! max(64, 0x100) = uextend.i64 v1
-    v3 ! mem(mt0, 0x100) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0x1000): i64, v1 ! range(32, 0, 0x1000): i32):
+    v2 ! range(64, 0, 0x100) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0x100) = iadd.i64 v0, v2
     v4 = load.i8 checked v3
     return v4
 }
@@ -34,8 +34,8 @@ block0(v0 ! mem(mt0, 0x1000): i64, v1 ! max(32, 0x1000): i32):
 ;; RegReg mode on aarch64.
 function %f3(i64, i64) -> i8 {
     mt0 = memory 0x100
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
-    v2 ! mem(mt0, 0xfff) = iadd.i64 v0, v1
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(64, 0, 0xfff): i64):
+    v2 ! mem(mt0, 0, 0xfff) = iadd.i64 v0, v1
     v3 = load.i8 checked v2
     return v3
 }
@@ -43,11 +43,11 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
 ;; RegScaledExtended mode on aarch64.
 function %f4(i64, i32) -> i64 {
     mt0 = memory 0x7000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
-    v2 ! max(64, 0xfff) = uextend.i64 v1
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xfff): i32):
+    v2 ! range(64, 0, 0xfff) = uextend.i64 v1
     v3 = iconst.i32 3
-    v4 ! max(64, 0x7ff8) = ishl.i64 v2, v3
-    v5 ! mem(mt0, 0x7ff8) = iadd.i64 v0, v4
+    v4 ! range(64, 0, 0x7ff8) = ishl.i64 v2, v3
+    v5 ! mem(mt0, 0, 0x7ff8) = iadd.i64 v0, v4
     v6 = load.i64 checked v5
     return v6
 }
@@ -55,10 +55,10 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
 ;; RegScaled mode on aarch64.
 function %f5(i64, i64) -> i64 {
     mt0 = memory 0x7000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(64, 0, 0xfff): i64):
     v2 = iconst.i32 3
-    v3 ! max(64, 0x7ff8) = ishl.i64 v1, v2
-    v4 ! mem(mt0, 0x7ff8) = iadd.i64 v0, v3
+    v3 ! range(64, 0, 0x7ff8) = ishl.i64 v1, v2
+    v4 ! mem(mt0, 0, 0x7ff8) = iadd.i64 v0, v3
     v5 = load.i64 checked v4
     return v5
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/memtypes.clif b/cranelift/filetests/filetests/pcc/fail/memtypes.clif
index 2a9c87f1b..bf4077792 100644
--- a/cranelift/filetests/filetests/pcc/fail/memtypes.clif
+++ b/cranelift/filetests/filetests/pcc/fail/memtypes.clif
@@ -5,25 +5,25 @@ target aarch64
 function %f0(i64) -> i32 {
     mt0 = struct 8 { 4: i32, 0: i32 } ; error: out-of-order
 
-block0(v0 ! mem(mt0, 0): i64):
+block0(v0 ! mem(mt0, 0, 0): i64):
     v1 = load.i32 v0+0
     return v1
 }
 
-function %f0(i64) -> i32 {
+function %f1(i64) -> i32 {
     ;; out-of-bounds field:
     mt0 = struct 8 { 0: i32, 6: i32 } ; error: field at offset 6 of size 4 that overflows
 
-block0(v0 ! mem(mt0, 0): i64):
+block0(v0 ! mem(mt0, 0, 0): i64):
     v1 = load.i32 v0+0
     return v1
 }
 
-function %f0(i64) -> i32 {
+function %f2(i64) -> i32 {
     ;; overflowing offset + field size:
     mt0 = struct 8 { 0: i32, 0xffff_ffff_ffff_ffff: i32 } ; error: field at offset 18446744073709551615 of size 4; offset plus size overflows a u64
 
-block0(v0 ! mem(mt0, 0): i64):
+block0(v0 ! mem(mt0, 0, 0): i64):
     v1 = load.i32 v0+0
     return v1
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/shift.clif b/cranelift/filetests/filetests/pcc/fail/shift.clif
index fba82900b..4059c3eba 100644
--- a/cranelift/filetests/filetests/pcc/fail/shift.clif
+++ b/cranelift/filetests/filetests/pcc/fail/shift.clif
@@ -3,8 +3,8 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32):
+block0(v0 ! range(32, 1, 0x100): i32):
   v1 = iconst.i32 2
-  v2 ! max(32, 0x3ff) = ishl.i32 v0, v1
+  v2 ! range(32, 4, 0x3ff) = ishl.i32 v0, v1
   return v2
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/simple.clif b/cranelift/filetests/filetests/pcc/fail/simple.clif
index 6196e4e31..777451212 100644
--- a/cranelift/filetests/filetests/pcc/fail/simple.clif
+++ b/cranelift/filetests/filetests/pcc/fail/simple.clif
@@ -7,9 +7,9 @@ target aarch64
 
 function %simple1(i64 vmctx, i32) -> i8 {
     mt0 = memory 0x8000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
-    v2 ! max(64, 0xffff_ffff) = uextend.i64 v1
-    v3 ! mem(mt0, 0xffff_ffff) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xffff_ffff): i32):
+    v2 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0xffff_ffff) = iadd.i64 v0, v2
     v4 = load.i8 checked v3
     return v4
 }
@@ -18,9 +18,9 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
 
 function %simple2(i64 vmctx, i32) -> i8 {
     mt0 = memory 0x8000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
-    v2 ! max(64, 0xffff_ffff) = uextend.i64 v1
-    v3 ! mem(mt0, 0) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xffff_ffff): i32):
+    v2 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0) = iadd.i64 v0, v2
     v4 = load.i8 checked v3
     return v4
 }
diff --git a/cranelift/filetests/filetests/pcc/fail/struct.clif b/cranelift/filetests/filetests/pcc/fail/struct.clif
index 22e455bea..c8d79dcdb 100644
--- a/cranelift/filetests/filetests/pcc/fail/struct.clif
+++ b/cranelift/filetests/filetests/pcc/fail/struct.clif
@@ -3,17 +3,26 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i64) -> i64 {
-    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0, 0) }
     mt1 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64):
-    v1 ! mem(mt1, 8) = load.i64 checked v0
+block0(v0 ! mem(mt0, 0, 0): i64):
+    v1 ! mem(mt1, 8, 8) = load.i64 checked v0
     return v1
 }
 
 function %f1(i64, i64) {
-    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0, 0) }
     mt1 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! mem(mt1, 8): i64):
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! mem(mt1, 8, 8): i64):
     store.i64 checked v1, v0
     return
 }
+
+function %f2(i64) -> i32 {
+    mt0 = struct 8 { 0: i32 ! range(32, 0, 1), 4: i32 }
+
+    ;; insufficiently-constrained range:
+block0(v0 ! mem(mt0, 0, 8): i64):
+    v1 ! range(32, 0, 1) = load.i32 checked v0+0
+    return v1
+}
diff --git a/cranelift/filetests/filetests/pcc/fail/vmctx.clif b/cranelift/filetests/filetests/pcc/fail/vmctx.clif
index f29e17344..9f53602f7 100644
--- a/cranelift/filetests/filetests/pcc/fail/vmctx.clif
+++ b/cranelift/filetests/filetests/pcc/fail/vmctx.clif
@@ -5,16 +5,16 @@ target aarch64
 ;; Equivalent to a Wasm `i64.load` from a static memory.
 function %f0(i64, i32) -> i64 {
     ;; mock vmctx struct:
-    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0, 0) }
     ;; mock static memory: 4GiB range, *but insufficient guard*
     mt1 = memory 0x1_0000_0000
 
-block0(v0 ! mem(mt0, 0): i64, v1: i32):
+block0(v0 ! mem(mt0, 0, 0): i64, v1: i32):
     ;; Compute the address: base + offset. Guard region (2GiB) is
     ;; sufficient for an 8-byte I64 load.
-    v2 ! mem(mt1, 0) = load.i64 checked v0+0    ;; base pointer
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
-    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v2 ! mem(mt1, 0, 0) = load.i64 checked v0+0    ;; base pointer
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0, 0xffff_ffff) = iadd.i64 v2, v3
     v5 = load.i64 checked v4
     return v5
 }
@@ -22,16 +22,16 @@ block0(v0 ! mem(mt0, 0): i64, v1: i32):
 ;; Equivalent to a Wasm `i64.load` from a static memory.
 function %f1(i64, i32) -> i64 {
     ;; mock vmctx struct:
-    mt0 = struct 16 { 0: i64 readonly ! mem(mt1, 0), 8: i64 readonly }
+    mt0 = struct 16 { 0: i64 readonly ! mem(mt1, 0, 0), 8: i64 readonly }
     ;; mock static memory: 4GiB range, *but insufficient guard*
     mt1 = memory 0x1_8000_0000
 
-block0(v0 ! mem(mt0, 0): i64, v1: i32):
+block0(v0 ! mem(mt0, 0, 0): i64, v1: i32):
     ;; Compute the address: base + offset. Guard region (2GiB) is
     ;; sufficient for an 8-byte I64 load.
-    v2 ! mem(mt1, 0) = load.i64 checked v0+8    ;; base pointer, but the wrong one
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
-    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v2 ! mem(mt1, 0, 0) = load.i64 checked v0+8    ;; base pointer, but the wrong one
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0, 0xffff_ffff) = iadd.i64 v2, v3
     v5 = load.i64 checked v4
     return v5
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/add.clif b/cranelift/filetests/filetests/pcc/succeed/add.clif
index 26e72c7c1..6cfcb486f 100644
--- a/cranelift/filetests/filetests/pcc/succeed/add.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/add.clif
@@ -3,58 +3,58 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32, i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(32, 0x80): i32):
-    v2 ! max(32, 0x180) = iadd.i32 v0, v1
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(32, 0, 0x80): i32):
+    v2 ! range(32, 0, 0x180) = iadd.i32 v0, v1
     return v2
 }
 
 function %f1(i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0x101) = iadd.i32 v0, v1
+block0(v0 ! range(32, 0, 0x100): i32):
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0x101) = iadd.i32 v0, v1
     return v2
 }
 
 ;; a looser but still accurate bound should check too:
 function %f2(i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0x102) = iadd.i32 v0, v1
+block0(v0 ! range(32, 0, 0x100): i32):
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0x102) = iadd.i32 v0, v1
     return v2
 }
 
 ;; we should be able to verify a range based on the type alone:
 function %f3(i32) -> i64 {
 block0(v0: i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0xffff_ffff) = iadd.i32 v0, v1
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v2
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0xffff_ffff) = iadd.i32 v0, v1
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v2
     return v3
 }
 
 ;; we should be able to verify a range based on the type alone:
 function %f3(i32) -> i64 {
 block0(v0: i32):
-    v1 ! max(32, 1) = iconst.i32 1
-    v2 ! max(32, 0xffff_ffff) = iadd.i32 v0, v1
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v2
-    v4 ! max(64, 0x1) = iconst.i64 1
-    v5 ! max(64, 0x1_0000_0000) = iadd.i64 v3, v4
+    v1 ! range(32, 0, 1) = iconst.i32 1
+    v2 ! range(32, 0, 0xffff_ffff) = iadd.i32 v0, v1
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v2
+    v4 ! range(64, 0, 0x1) = iconst.i64 1
+    v5 ! range(64, 0, 0x1_0000_0000) = iadd.i64 v3, v4
     return v5
 }
 
 ;; check merged ops:
 function %f4(i32, i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(32, 0x200): i32):
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(32, 0, 0x200): i32):
   v2 = iconst.i32 2
-  v3 ! max(32, 0x400) = ishl.i32 v0, v2
-  v4 ! max(32, 0x600) = iadd.i32 v1, v3
+  v3 ! range(32, 0, 0x400) = ishl.i32 v0, v2
+  v4 ! range(32, 0, 0x600) = iadd.i32 v1, v3
   return v4
 }
 
 function %f5(i32, i64) -> i64 {
-block0(v0 ! max(32, 0x100): i32, v1 ! max(64, 0x200): i64):
-  v2 ! max(64, 0x100) = uextend.i64 v0
-  v3 ! max(64, 0x300) = iadd.i64 v1, v2
+block0(v0 ! range(32, 0, 0x100): i32, v1 ! range(64, 0, 0x200): i64):
+  v2 ! range(64, 0, 0x100) = uextend.i64 v0
+  v3 ! range(64, 0, 0x300) = iadd.i64 v1, v2
   return v3
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/blockparams.clif b/cranelift/filetests/filetests/pcc/succeed/blockparams.clif
index 092c1838c..914edf12a 100644
--- a/cranelift/filetests/filetests/pcc/succeed/blockparams.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/blockparams.clif
@@ -3,17 +3,17 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i64, i32) -> i64 {
-block0(v0 ! max(64, 0x100): i64, v1: i32):
-    v2 ! max(64, 0x100) = iconst.i64 0x100
-    v3 ! max(64, 0x200) = iadd v0, v2
+block0(v0 ! range(64, 0, 0x100): i64, v1: i32):
+    v2 ! range(64, 0, 0x100) = iconst.i64 0x100
+    v3 ! range(64, 0, 0x200) = iadd v0, v2
     brif v1, block1(v0), block2(v3)
 
-block1(v4 ! max(64, 0x1000): i64):  ;; broaden the range -- always allowed
+block1(v4 ! range(64, 0, 0x1000): i64):  ;; broaden the range -- always allowed
     jump block3(v4)
 
-block2(v5 ! max(64, 0x2000): i64):
+block2(v5 ! range(64, 0, 0x2000): i64):
     jump block3(v5)
 
-block3(v6 ! max(64, 0x2001): i64):
+block3(v6 ! range(64, 0, 0x2001): i64):
     return v6
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/extend.clif b/cranelift/filetests/filetests/pcc/succeed/extend.clif
index 6f89f9309..690e3d0ac 100644
--- a/cranelift/filetests/filetests/pcc/succeed/extend.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/extend.clif
@@ -3,7 +3,8 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32) -> i64 {
-block0(v0 ! max(32, 0xffff_ffff): i32):
-  v1 ! max(64, 0xffff_ffff) = uextend.i64 v0
+block0(v0 ! range(32, 42, 0xffff_fffe): i32):
+  ;; we're allowed to broaden the range on either end:
+  v1 ! range(64, 1, 0xffff_ffff) = uextend.i64 v0
   return v1
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/load.clif b/cranelift/filetests/filetests/pcc/succeed/load.clif
index 6766c7a28..276eab361 100644
--- a/cranelift/filetests/filetests/pcc/succeed/load.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/load.clif
@@ -4,9 +4,9 @@ target aarch64
 
 function %f0(i64, i32) -> i64 {
     mt0 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0x100): i32):
-    v2 ! max(64, 0x100) = uextend.i64 v1
-    v3 ! mem(mt0, 8) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0x100): i32):
+    v2 ! range(64, 0, 0x100) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 8) = iadd.i64 v0, v2
     v4 = load.i64 checked v3
     return v4
 }
@@ -14,9 +14,9 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0x100): i32):
 function %f1(i64, i32) -> i64 {
     ;; Note the guard region of 8 bytes -- just enough for the below!
     mt0 = memory 0x1_0000_0008
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
-    v2 ! max(64, 0xffff_ffff) = uextend.i64 v1
-    v3 ! mem(mt0, 0xffff_ffff) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xffff_ffff): i32):
+    v2 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0xffff_ffff) = iadd.i64 v0, v2
     v4 = load.i64 checked v3
     return v4
 }
@@ -24,9 +24,9 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
 ;; RegRegExtend mode on aarch64.
 function %f2(i64, i32) -> i8 {
     mt0 = memory 0x1000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
-    v2 ! max(64, 0x100) = uextend.i64 v1
-    v3 ! mem(mt0, 0x100) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xfff): i32):
+    v2 ! range(64, 0, 0x100) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0x100) = iadd.i64 v0, v2
     v4 = load.i8 checked v3
     return v4
 }
@@ -34,8 +34,8 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
 ;; RegReg mode on aarch64.
 function %f3(i64, i64) -> i8 {
     mt0 = memory 0x1000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
-    v2 ! mem(mt0, 0xfff) = iadd.i64 v0, v1
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(64, 0, 0xfff): i64):
+    v2 ! mem(mt0, 0, 0xfff) = iadd.i64 v0, v1
     v3 = load.i8 checked v2
     return v3
 }
@@ -43,11 +43,11 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
 ;; RegScaledExtended mode on aarch64.
 function %f4(i64, i32) -> i64 {
     mt0 = memory 0x8000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
-    v2 ! max(64, 0xfff) = uextend.i64 v1
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xfff): i32):
+    v2 ! range(64, 0, 0xfff) = uextend.i64 v1
     v3 = iconst.i32 3
-    v4 ! max(64, 0x7ff8) = ishl.i64 v2, v3
-    v5 ! mem(mt0, 0x7ff8) = iadd.i64 v0, v4
+    v4 ! range(64, 0, 0x7ff8) = ishl.i64 v2, v3
+    v5 ! mem(mt0, 0, 0x7ff8) = iadd.i64 v0, v4
     v6 = load.i64 checked v5
     return v6
 }
@@ -55,10 +55,10 @@ block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xfff): i32):
 ;; RegScaled mode on aarch64.
 function %f5(i64, i64) -> i64 {
     mt0 = memory 0x8000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(64, 0xfff): i64):
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(64, 0, 0xfff): i64):
     v2 = iconst.i32 3
-    v3 ! max(64, 0x7ff8) = ishl.i64 v1, v2
-    v4 ! mem(mt0, 0x7ff8) = iadd.i64 v0, v3
+    v3 ! range(64, 0, 0x7ff8) = ishl.i64 v1, v2
+    v4 ! mem(mt0, 0, 0x7ff8) = iadd.i64 v0, v3
     v5 = load.i64 checked v4
     return v5
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/memtypes.clif b/cranelift/filetests/filetests/pcc/succeed/memtypes.clif
index 9849ea00e..ea35dcdbf 100644
--- a/cranelift/filetests/filetests/pcc/succeed/memtypes.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/memtypes.clif
@@ -5,7 +5,7 @@ target aarch64
 function %f0(i64) -> i32 {
     mt0 = struct 8 { 0: i32, 4: i32 readonly }
 
-block0(v0 ! mem(mt0, 0): i64):  ;; v0 points to an instance of mt0, at offset 0
+block0(v0 ! mem(mt0, 0, 0): i64):  ;; v0 points to an instance of mt0, at offset 0
     v1 = load.i32 v0+0
     v2 = load.i32 v0+4
     v3 = iadd.i32 v1, v2
@@ -13,11 +13,11 @@ block0(v0 ! mem(mt0, 0): i64):  ;; v0 points to an instance of mt0, at offset 0
 }
 
 function %f1(i64) -> i32 {
-    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0, 0) }
     mt1 = memory 0x1_0000_0000
 
-block0(v0 ! mem(mt0, 0): i64):
-    v1 ! mem(mt1, 0) = load.i64 v0
+block0(v0 ! mem(mt0, 0, 0): i64):
+    v1 ! mem(mt1, 0, 0) = load.i64 v0
     v2 = load.i32 v1+0x1000
     return v2
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/shift.clif b/cranelift/filetests/filetests/pcc/succeed/shift.clif
index d4ebd25fc..685d57497 100644
--- a/cranelift/filetests/filetests/pcc/succeed/shift.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/shift.clif
@@ -3,15 +3,15 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i32) -> i32 {
-block0(v0 ! max(32, 0x100): i32):
+block0(v0 ! range(32, 1, 0x100): i32):
   v1 = iconst.i32 2
-  v2 ! max(32, 0x400) = ishl.i32 v0, v1
+  v2 ! range(32, 4, 0x400) = ishl.i32 v0, v1
   return v2
 }
 
 function %f0(i32) -> i32 {
 block0(v0: i32):
   v1 = iconst.i32 2
-  v2 ! max(32, 0xffff_ffff) = ishl.i32 v0, v1
+  v2 ! range(32, 0, 0xffff_ffff) = ishl.i32 v0, v1
   return v2
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/simple.clif b/cranelift/filetests/filetests/pcc/succeed/simple.clif
index 241124e67..2baa3f991 100644
--- a/cranelift/filetests/filetests/pcc/succeed/simple.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/simple.clif
@@ -4,9 +4,9 @@ target aarch64
 
 function %simple1(i64 vmctx, i32) -> i8 {
     mt0 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! max(32, 0xffff_ffff): i32):
-    v2 ! max(64, 0xffff_ffff) = uextend.i64 v1
-    v3 ! mem(mt0, 0xffff_ffff) = iadd.i64 v0, v2
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! range(32, 0, 0xffff_ffff): i32):
+    v2 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1
+    v3 ! mem(mt0, 0, 0xffff_ffff) = iadd.i64 v0, v2
     v4 = load.i8 checked v3
     return v4
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/struct.clif b/cranelift/filetests/filetests/pcc/succeed/struct.clif
index 6555b22ad..1d7c56882 100644
--- a/cranelift/filetests/filetests/pcc/succeed/struct.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/struct.clif
@@ -3,17 +3,17 @@ set enable_pcc=true
 target aarch64
 
 function %f0(i64) -> i64 {
-    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0, 0) }
     mt1 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64):
-    v1 ! mem(mt1, 0) = load.i64 checked v0
+block0(v0 ! mem(mt0, 0, 0): i64):
+    v1 ! mem(mt1, 0, 0) = load.i64 checked v0
     return v1
 }
 
 function %f1(i64, i64) {
-    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0, 0) }
     mt1 = memory 0x1_0000_0000
-block0(v0 ! mem(mt0, 0): i64, v1 ! mem(mt1, 0): i64):
+block0(v0 ! mem(mt0, 0, 0): i64, v1 ! mem(mt1, 0, 0): i64):
     store.i64 checked v1, v0
     return
 }
diff --git a/cranelift/filetests/filetests/pcc/succeed/vmctx.clif b/cranelift/filetests/filetests/pcc/succeed/vmctx.clif
index 974718293..643984fa7 100644
--- a/cranelift/filetests/filetests/pcc/succeed/vmctx.clif
+++ b/cranelift/filetests/filetests/pcc/succeed/vmctx.clif
@@ -5,16 +5,16 @@ target aarch64
 ;; Equivalent to a Wasm `i64.load` from a static memory.
 function %f0(i64, i32) -> i64 {
     ;; mock vmctx struct:
-    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0) }
+    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0, 0) }
     ;; mock static memory: 4GiB range, plus 2GiB guard
     mt1 = memory 0x1_8000_0000
 
-block0(v0 ! mem(mt0, 0): i64, v1: i32):
+block0(v0 ! mem(mt0, 0, 0): i64, v1: i32):
     ;; Compute the address: base + offset. Guard region (2GiB) is
     ;; sufficient for an 8-byte I64 load.
-    v2 ! mem(mt1, 0) = load.i64 checked v0+0    ;; base pointer
-    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
-    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v2 ! mem(mt1, 0, 0) = load.i64 checked v0+0    ;; base pointer
+    v3 ! range(64, 0, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0, 0xffff_ffff) = iadd.i64 v2, v3
     v5 = load.i64 checked v4
     return v5
 }
diff --git a/cranelift/filetests/filetests/runtests/urem.clif b/cranelift/filetests/filetests/runtests/urem.clif
index 42297ad37..3fa202424 100644
--- a/cranelift/filetests/filetests/runtests/urem.clif
+++ b/cranelift/filetests/filetests/runtests/urem.clif
@@ -135,3 +135,13 @@ block0(v0: i8):
 ; run: %urem_imm_i8(-19) == 0
 ; run: %urem_imm_i8(0xC0) == 0
 ; run: %urem_imm_i8(0x80) == 2
+
+
+function %constant_inputs() -> i8  {
+block0:
+    v3 = iconst.i8 208
+    v11 = urem v3, v3  ; v3 = 208, v3 = 208
+    return v11
+}
+
+; run: %constant_inputs() == 0