Repository: bytecodealliance/wasmtime
Pull request #8954 (2024-07-13 20:16:36)

Cranelift: Constant propagate floats
Adds constant propagation of the following instructions for `$F32` and `$F64`:
`fadd`, `fsub`, `fmul`, `fdiv`, `sqrt`, `ceil`, `floor`, `trunc`, `nearest`.

`fmin` and `fmax` are still missing. Those are tricky due to their handling and propagation of NaNs: [cranelift_codegen::ir::InstBuilder::fmin](https://docs.rs/cranelift-codegen/latest/cranelift_codegen/ir/trait.InstBuilder.html#method.fmin)


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>
fitzgen: I would be more comfortable with aborting the compile-time evaluation if either given or producing a `NaN`. It is more conservative, and we can always have follow up discussions/PRs focused on just that issue. I also suspect there isn't too much additional performance to gain w.r.t `NaN`s.

----------

FWIW, it seems like compile-time evaluating relaxed SIMD instructions like `fma` is probably a no-go: https://github.com/WebAssembly/relaxed-simd/issues/155#issuecomment-2232721287
fitzgen: > Regarding `fma`: I still believe that it is a deterministic instruction when not involving NaNs, but since this PR doesn’t include it, this is something we can discuss in the future.

Yes, my b I was thinking of the `relaxed_madd` Wasm instruction.

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8a1f38fe5..a97b6828e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -467,7 +467,7 @@ jobs:
     - run: git diff --exit-code
 
     # Re-vendor the C API and make sure it's up-to-date.
-    - name: Re-vendor WIT
+    - name: Re-vendor C API
       run: ./ci/vendor-c-api-headers.sh
     - run: git diff --exit-code
 
diff --git a/Cargo.toml b/Cargo.toml
index 8217a90de..5b94be104 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -350,6 +350,7 @@ default = [
   "wasi-nn",
   "wasi-threads",
   "wasi-http",
+  "wasi-runtime-config",
 
   # Most features of Wasmtime are enabled by default.
   "wat",
@@ -395,6 +396,7 @@ disable-logging = ["log/max_level_off", "tracing/max_level_off"]
 wasi-nn = ["dep:wasmtime-wasi-nn"]
 wasi-threads = ["dep:wasmtime-wasi-threads", "threads"]
 wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
+wasi-runtime-config = ["dep:wasmtime-wasi-runtime-config"]
 pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
 component-model = [
   "wasmtime/component-model",
diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs
index ffc212e41..24982c644 100644
--- a/cranelift/codegen/src/ir/immediates.rs
+++ b/cranelift/codegen/src/ir/immediates.rs
@@ -915,6 +915,11 @@ impl Ieee32 {
         self.as_f32().is_nan()
     }
 
+    /// Returns `None` if `self` is a NaN and `Some(self)` otherwise.
+    pub fn non_nan(self) -> Option<Self> {
+        Some(self).filter(|f| !f.is_nan())
+    }
+
     /// Converts Self to a rust f32
     pub fn as_f32(self) -> f32 {
         f32::from_bits(self.0)
@@ -927,17 +932,21 @@ impl Ieee32 {
 
     /// Computes the absolute value of self.
     pub fn abs(self) -> Self {
-        Self::with_float(self.as_f32().abs())
+        Self(self.0 & !(1u32 << 31))
     }
 
     /// Returns a number composed of the magnitude of self and the sign of sign.
     pub fn copysign(self, sign: Self) -> Self {
-        Self::with_float(self.as_f32().copysign(sign.as_f32()))
+        if self.is_negative() == sign.is_negative() {
+            self
+        } else {
+            self.neg()
+        }
     }
 
     /// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
     pub fn is_negative(&self) -> bool {
-        self.as_f32().is_sign_negative()
+        self.0 & (1 << 31) != 0
     }
 
     /// Returns true if self is positive or negative zero
@@ -963,17 +972,7 @@ impl Ieee32 {
     /// Returns the nearest integer to `self`. Rounds half-way cases to the number
     /// with an even least significant digit.
     pub fn round_ties_even(self) -> Self {
-        // TODO: Replace with the native implementation once
-        // https://github.com/rust-lang/rust/issues/96710 is stabilized
-        let toint_32: f32 = 1.0 / f32::EPSILON;
-
-        let f = self.as_f32();
-        let e = self.0 >> 23 & 0xff;
-        if e >= 0x7f_u32 + 23 {
-            self
-        } else {
-            Self::with_float((f.abs() + toint_32 - toint_32).copysign(f))
-        }
+        Self::with_float(self.as_f32().round_ties_even())
     }
 }
 
@@ -1017,7 +1016,7 @@ impl Neg for Ieee32 {
     type Output = Ieee32;
 
     fn neg(self) -> Self::Output {
-        Self::with_float(self.as_f32().neg())
+        Self(self.0 ^ (1 << 31))
     }
 }
 
@@ -1133,6 +1132,11 @@ impl Ieee64 {
         self.as_f64().is_nan()
     }
 
+    /// Returns `None` if `self` is a NaN and `Some(self)` otherwise.
+    pub fn non_nan(self) -> Option<Self> {
+        Some(self).filter(|f| !f.is_nan())
+    }
+
     /// Converts Self to a rust f64
     pub fn as_f64(self) -> f64 {
         f64::from_bits(self.0)
@@ -1145,17 +1149,21 @@ impl Ieee64 {
 
     /// Computes the absolute value of self.
     pub fn abs(self) -> Self {
-        Self::with_float(self.as_f64().abs())
+        Self(self.0 & !(1u64 << 63))
     }
 
     /// Returns a number composed of the magnitude of self and the sign of sign.
     pub fn copysign(self, sign: Self) -> Self {
-        Self::with_float(self.as_f64().copysign(sign.as_f64()))
+        if self.is_negative() == sign.is_negative() {
+            self
+        } else {
+            self.neg()
+        }
     }
 
     /// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
     pub fn is_negative(&self) -> bool {
-        self.as_f64().is_sign_negative()
+        self.0 & (1 << 63) != 0
     }
 
     /// Returns true if self is positive or negative zero
@@ -1181,17 +1189,7 @@ impl Ieee64 {
     /// Returns the nearest integer to `self`. Rounds half-way cases to the number
     /// with an even least significant digit.
     pub fn round_ties_even(self) -> Self {
-        // TODO: Replace with the native implementation once
-        // https://github.com/rust-lang/rust/issues/96710 is stabilized
-        let toint_64: f64 = 1.0 / f64::EPSILON;
-
-        let f = self.as_f64();
-        let e = self.0 >> 52 & 0x7ff_u64;
-        if e >= 0x3ff_u64 + 52 {
-            self
-        } else {
-            Self::with_float((f.abs() + toint_64 - toint_64).copysign(f))
-        }
+        Self::with_float(self.as_f64().round_ties_even())
     }
 }
 
@@ -1241,7 +1239,7 @@ impl Neg for Ieee64 {
     type Output = Ieee64;
 
     fn neg(self) -> Self::Output {
-        Self::with_float(self.as_f64().neg())
+        Self(self.0 ^ (1 << 63))
     }
 }
 
diff --git a/cranelift/codegen/src/ir/memflags.rs b/cranelift/codegen/src/ir/memflags.rs
index e666de7b4..9b706bd56 100644
--- a/cranelift/codegen/src/ir/memflags.rs
+++ b/cranelift/codegen/src/ir/memflags.rs
@@ -19,11 +19,33 @@ pub enum Endianness {
 /// Which disjoint region of aliasing memory is accessed in this memory
 /// operation.
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+#[repr(u8)]
 #[allow(missing_docs)]
+#[rustfmt::skip]
 pub enum AliasRegion {
-    Heap,
-    Table,
-    Vmctx,
+    // None = 0b00;
+    Heap    = 0b01,
+    Table   = 0b10,
+    Vmctx   = 0b11,
+}
+
+impl AliasRegion {
+    const fn from_bits(bits: u8) -> Option<Self> {
+        match bits {
+            0b00 => None,
+            0b01 => Some(Self::Heap),
+            0b10 => Some(Self::Table),
+            0b11 => Some(Self::Vmctx),
+            _ => panic!("invalid alias region bits"),
+        }
+    }
+
+    const fn to_bits(region: Option<Self>) -> u8 {
+        match region {
+            None => 0b00,
+            Some(r) => r as u8,
+        }
+    }
 }
 
 /// Flags for memory operations like load/store.
@@ -114,27 +136,14 @@ impl MemFlags {
 
     /// Reads the alias region that this memory operation works with.
     pub const fn alias_region(self) -> Option<AliasRegion> {
-        // NB: keep in sync with `with_alias_region`
-        match (self.bits & MASK_ALIAS_REGION) >> ALIAS_REGION_OFFSET {
-            0b00 => None,
-            0b01 => Some(AliasRegion::Heap),
-            0b10 => Some(AliasRegion::Table),
-            0b11 => Some(AliasRegion::Vmctx),
-            _ => unreachable!(),
-        }
+        AliasRegion::from_bits(((self.bits & MASK_ALIAS_REGION) >> ALIAS_REGION_OFFSET) as u8)
     }
 
     /// Sets the alias region that this works on to the specified `region`.
     pub const fn with_alias_region(mut self, region: Option<AliasRegion>) -> Self {
-        // NB: keep in sync with `alias_region`
-        let bits = match region {
-            None => 0b00,
-            Some(AliasRegion::Heap) => 0b01,
-            Some(AliasRegion::Table) => 0b10,
-            Some(AliasRegion::Vmctx) => 0b11,
-        };
+        let bits = AliasRegion::to_bits(region);
         self.bits &= !MASK_ALIAS_REGION;
-        self.bits |= bits << ALIAS_REGION_OFFSET;
+        self.bits |= (bits as u16) << ALIAS_REGION_OFFSET;
         self
     }
 
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index 25534059d..5c5ea82c1 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -165,9 +165,9 @@ pub(crate) fn emit(
             let mut rex = RexFlags::from(*size);
             let (opcode_r, opcode_m, subopcode_i) = match op {
                 AluRmiROpcode::Add => (0x01, 0x03, 0),
-                AluRmiROpcode::Adc => (0x11, 0x03, 0),
+                AluRmiROpcode::Adc => (0x11, 0x03, 2),
                 AluRmiROpcode::Sub => (0x29, 0x2B, 5),
-                AluRmiROpcode::Sbb => (0x19, 0x2B, 5),
+                AluRmiROpcode::Sbb => (0x19, 0x2B, 3),
                 AluRmiROpcode::And => (0x21, 0x23, 4),
                 AluRmiROpcode::Or => (0x09, 0x0B, 1),
                 AluRmiROpcode::Xor => (0x31, 0x33, 6),
diff --git a/cranelift/codegen/src/isle_prelude.rs b/cranelift/codegen/src/isle_prelude.rs
index f36c4bdb0..530cfb139 100644
--- a/cranelift/codegen/src/isle_prelude.rs
+++ b/cranelift/codegen/src/isle_prelude.rs
@@ -950,6 +950,62 @@ macro_rules! isle_common_prelude_methods {
             a.copysign(b)
         }
 
+        fn f32_add(&mut self, lhs: Ieee32, rhs: Ieee32) -> Option<Ieee32> {
+            (lhs + rhs).non_nan()
+        }
+
+        fn f32_sub(&mut self, lhs: Ieee32, rhs: Ieee32) -> Option<Ieee32> {
+            (lhs - rhs).non_nan()
+        }
+
+        fn f32_mul(&mut self, lhs: Ieee32, rhs: Ieee32) -> Option<Ieee32> {
+            (lhs * rhs).non_nan()
+        }
+
+        fn f32_div(&mut self, lhs: Ieee32, rhs: Ieee32) -> Option<Ieee32> {
+            (lhs / rhs).non_nan()
+        }
+
+        fn f32_sqrt(&mut self, n: Ieee32) -> Option<Ieee32> {
+            n.sqrt().non_nan()
+        }
+
+        fn f32_ceil(&mut self, n: Ieee32) -> Option<Ieee32> {
+            n.ceil().non_nan()
+        }
+
+        fn f32_floor(&mut self, n: Ieee32) -> Option<Ieee32> {
+            n.floor().non_nan()
+        }
+
+        fn f32_trunc(&mut self, n: Ieee32) -> Option<Ieee32> {
+            n.trunc().non_nan()
+        }
+
+        fn f32_nearest(&mut self, n: Ieee32) -> Option<Ieee32> {
+            n.round_ties_even().non_nan()
+        }
+
+        fn f32_min(&mut self, a: Ieee32, b: Ieee32) -> Option<Ieee32> {
+            if a.is_nan() || b.is_nan() {
+                None
+            } else if a <= b {
+                Some(a)
+            } else {
+                Some(b)
+            }
+        }
+
+        fn f32_max(&mut self, a: Ieee32, b: Ieee32) -> Option<Ieee32> {
+            if a.is_nan() || b.is_nan() {
+                None
+            } else if a >= b {
+                Some(a)
+            } else {
+                Some(b)
+            }
+        }
+
         fn f32_neg(&mut self, n: Ieee32) -> Ieee32 {
             n.neg()
         }
@@ -962,6 +1018,62 @@ macro_rules! isle_common_prelude_methods {
             a.copysign(b)
         }
 
+        fn f64_add(&mut self, lhs: Ieee64, rhs: Ieee64) -> Option<Ieee64> {
+            (lhs + rhs).non_nan()
+        }
+
+        fn f64_sub(&mut self, lhs: Ieee64, rhs: Ieee64) -> Option<Ieee64> {
+            (lhs - rhs).non_nan()
+        }
+
+        fn f64_mul(&mut self, lhs: Ieee64, rhs: Ieee64) -> Option<Ieee64> {
+            (lhs * rhs).non_nan()
+        }
+
+        fn f64_div(&mut self, lhs: Ieee64, rhs: Ieee64) -> Option<Ieee64> {
+            (lhs / rhs).non_nan()
+        }
+
+        fn f64_sqrt(&mut self, n: Ieee64) -> Option<Ieee64> {
+            n.sqrt().non_nan()
+        }
+
+        fn f64_ceil(&mut self, n: Ieee64) -> Option<Ieee64> {
+            n.ceil().non_nan()
+        }
+
+        fn f64_floor(&mut self, n: Ieee64) -> Option<Ieee64> {
+            n.floor().non_nan()
+        }
+
+        fn f64_trunc(&mut self, n: Ieee64) -> Option<Ieee64> {
+            n.trunc().non_nan()
+        }
+
+        fn f64_nearest(&mut self, n: Ieee64) -> Option<Ieee64> {
+            n.round_ties_even().non_nan()
+        }
+
+        fn f64_min(&mut self, a: Ieee64, b: Ieee64) -> Option<Ieee64> {
+            if a.is_nan() || b.is_nan() {
+                None
+            } else if a <= b {
+                Some(a)
+            } else {
+                Some(b)
+            }
+        }
+
+        fn f64_max(&mut self, a: Ieee64, b: Ieee64) -> Option<Ieee64> {
+            if a.is_nan() || b.is_nan() {
+                None
+            } else if a >= b {
+                Some(a)
+            } else {
+                Some(b)
+            }
+        }
+
         fn f64_neg(&mut self, n: Ieee64) -> Ieee64 {
             n.neg()
         }
diff --git a/cranelift/codegen/src/opts/cprop.isle b/cranelift/codegen/src/opts/cprop.isle
index 9b31b0e7e..a9685be75 100644
--- a/cranelift/codegen/src/opts/cprop.isle
+++ b/cranelift/codegen/src/opts/cprop.isle
@@ -182,8 +182,6 @@
       (if-let $true (u64_lt (i64_as_u64 (i64_neg k)) (i64_as_u64 k)))
       (iadd ty x (iconst ty (imm64_masked ty (i64_as_u64 (i64_neg k))))))
 
-;; TODO: fadd, fsub, fmul, fdiv, fneg, fabs
-
 ;; A splat of a constant can become a direct `vconst` with the appropriate bit
 ;; pattern.
 (rule (simplify (splat dst (iconst $I8 n)))
@@ -280,7 +278,92 @@
 (decl pure u64_bswap64 (u64) u64)
 (extern constructor u64_bswap64 u64_bswap64)
 
-;; Constant fold bitwise float operations (fneg/fabs/fcopysign)
+;; Constant fold float operations
+;; Note: With the exception of fabs, fneg and copysign,
+;; constant folding is only performed when the result of
+;; an instruction isn't NaN. We want the NaN bit patterns
+;; produced by an instruction to be consistent, and
+;; compile-time evaluation in a cross-compilation scenario
+;; risks producing different NaN bit patterns than the target
+;; would have at run-time.
+;; TODO: fcmp, fma, demote, promote, to-int ops
+(rule (simplify (fadd $F32 (f32const $F32 lhs) (f32const $F32 rhs)))
+      (if-let r (f32_add lhs rhs))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fadd $F64 (f64const $F64 lhs) (f64const $F64 rhs)))
+      (if-let r (f64_add lhs rhs))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (fsub $F32 (f32const $F32 lhs) (f32const $F32 rhs)))
+      (if-let r (f32_sub lhs rhs))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fsub $F64 (f64const $F64 lhs) (f64const $F64 rhs)))
+      (if-let r (f64_sub lhs rhs))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (fmul $F32 (f32const $F32 lhs) (f32const $F32 rhs)))
+      (if-let r (f32_mul lhs rhs))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fmul $F64 (f64const $F64 lhs) (f64const $F64 rhs)))
+      (if-let r (f64_mul lhs rhs))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (fdiv $F32 (f32const $F32 lhs) (f32const $F32 rhs)))
+      (if-let r (f32_div lhs rhs))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fdiv $F64 (f64const $F64 lhs) (f64const $F64 rhs)))
+      (if-let r (f64_div lhs rhs))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (sqrt $F32 (f32const $F32 n)))
+      (if-let r (f32_sqrt n))
+      (subsume (f32const $F32 r)))
+(rule (simplify (sqrt $F64 (f64const $F64 n)))
+      (if-let r (f64_sqrt n))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (ceil $F32 (f32const $F32 n)))
+      (if-let r (f32_ceil n))
+      (subsume (f32const $F32 r)))
+(rule (simplify (ceil $F64 (f64const $F64 n)))
+      (if-let r (f64_ceil n))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (floor $F32 (f32const $F32 n)))
+      (if-let r (f32_floor n))
+      (subsume (f32const $F32 r)))
+(rule (simplify (floor $F64 (f64const $F64 n)))
+      (if-let r (f64_floor n))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (trunc $F32 (f32const $F32 n)))
+      (if-let r (f32_trunc n))
+      (subsume (f32const $F32 r)))
+(rule (simplify (trunc $F64 (f64const $F64 n)))
+      (if-let r (f64_trunc n))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (nearest $F32 (f32const $F32 n)))
+      (if-let r (f32_nearest n))
+      (subsume (f32const $F32 r)))
+(rule (simplify (nearest $F64 (f64const $F64 n)))
+      (if-let r (f64_nearest n))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (fmin $F32 (f32const $F32 n) (f32const $F32 m)))
+      (if-let r (f32_min n m))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fmin $F64 (f64const $F64 n) (f64const $F64 m)))
+      (if-let r (f64_min n m))
+      (subsume (f64const $F64 r)))
+
+(rule (simplify (fmax $F32 (f32const $F32 n) (f32const $F32 m)))
+      (if-let r (f32_max n m))
+      (subsume (f32const $F32 r)))
+(rule (simplify (fmax $F64 (f64const $F64 n) (f64const $F64 m)))
+      (if-let r (f64_max n m))
+      (subsume (f64const $F64 r)))
+
 (rule (simplify (fneg $F16 (f16const $F16 n)))
       (subsume (f16const $F16 (f16_neg n))))
 (rule (simplify (fneg $F32 (f32const $F32 n)))
diff --git a/cranelift/codegen/src/prelude.isle b/cranelift/codegen/src/prelude.isle
index 95f31bfdb..233ab899e 100644
--- a/cranelift/codegen/src/prelude.isle
+++ b/cranelift/codegen/src/prelude.isle
@@ -245,12 +245,56 @@
 (extern constructor f16_abs f16_abs)
 (decl pure f16_copysign (Ieee16 Ieee16) Ieee16)
 (extern constructor f16_copysign f16_copysign)
+(decl pure partial f32_add (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_add f32_add)
+(decl pure partial f32_sub (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_sub f32_sub)
+(decl pure partial f32_mul (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_mul f32_mul)
+(decl pure partial f32_div (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_div f32_div)
+(decl pure partial f32_sqrt (Ieee32) Ieee32)
+(extern constructor f32_sqrt f32_sqrt)
+(decl pure partial f32_ceil (Ieee32) Ieee32)
+(extern constructor f32_ceil f32_ceil)
+(decl pure partial f32_floor (Ieee32) Ieee32)
+(extern constructor f32_floor f32_floor)
+(decl pure partial f32_trunc (Ieee32) Ieee32)
+(extern constructor f32_trunc f32_trunc)
+(decl pure partial f32_nearest (Ieee32) Ieee32)
+(extern constructor f32_nearest f32_nearest)
+(decl pure partial f32_min (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_min f32_min)
+(decl pure partial f32_max (Ieee32 Ieee32) Ieee32)
+(extern constructor f32_max f32_max)
 (decl pure f32_neg (Ieee32) Ieee32)
 (extern constructor f32_neg f32_neg)
 (decl pure f32_abs (Ieee32) Ieee32)
 (extern constructor f32_abs f32_abs)
 (decl pure f32_copysign (Ieee32 Ieee32) Ieee32)
 (extern constructor f32_copysign f32_copysign)
+(decl pure partial f64_add (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_add f64_add)
+(decl pure partial f64_sub (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_sub f64_sub)
+(decl pure partial f64_mul (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_mul f64_mul)
+(decl pure partial f64_div (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_div f64_div)
+(decl pure partial f64_sqrt (Ieee64) Ieee64)
+(extern constructor f64_sqrt f64_sqrt)
+(decl pure partial f64_ceil (Ieee64) Ieee64)
+(extern constructor f64_ceil f64_ceil)
+(decl pure partial f64_floor (Ieee64) Ieee64)
+(extern constructor f64_floor f64_floor)
+(decl pure partial f64_trunc (Ieee64) Ieee64)
+(extern constructor f64_trunc f64_trunc)
+(decl pure partial f64_nearest (Ieee64) Ieee64)
+(extern constructor f64_nearest f64_nearest)
+(decl pure partial f64_min (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_min f64_min)
+(decl pure partial f64_max (Ieee64 Ieee64) Ieee64)
+(extern constructor f64_max f64_max)
 (decl pure f64_neg (Ieee64) Ieee64)
 (extern constructor f64_neg f64_neg)
 (decl pure f64_abs (Ieee64) Ieee64)
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index d60bd2d26..cab0382b2 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -284,6 +284,8 @@ wasmtime_option_group! {
         pub threads: Option<bool>,
         /// Enable support for WASI HTTP API (experimental)
         pub http: Option<bool>,
+        /// Enable support for WASI runtime config API (experimental)
+        pub runtime_config: Option<bool>,
         /// Inherit environment variables and file descriptors following the
         /// systemd listen fd specification (UNIX only)
         pub listenfd: Option<bool>,
@@ -321,6 +323,8 @@ wasmtime_option_group! {
         ///
         /// This option can be further overwritten with `--env` flags.
         pub inherit_env: Option<bool>,
+        /// Pass a wasi runtime config variable to the program.
+        pub runtime_config_var: Vec<WasiRuntimeConfigVariable>,
     }
 
     enum Wasi {
@@ -334,6 +338,12 @@ pub struct WasiNnGraph {
     pub dir: String,
 }
 
+#[derive(Debug, Clone, PartialEq)]
+pub struct WasiRuntimeConfigVariable {
+    pub key: String,
+    pub value: String,
+}
+
 /// Common options for commands that translate WebAssembly modules
 #[derive(Parser, Clone)]
 pub struct CommonOptions {
diff --git a/crates/cli-flags/src/opt.rs b/crates/cli-flags/src/opt.rs
index 138103945..55b599d6e 100644
--- a/crates/cli-flags/src/opt.rs
+++ b/crates/cli-flags/src/opt.rs
@@ -5,7 +5,7 @@
 //! specifying options in a struct-like syntax where all other boilerplate about
 //! option parsing is contained exclusively within this module.
 
-use crate::WasiNnGraph;
+use crate::{WasiNnGraph, WasiRuntimeConfigVariable};
 use anyhow::{bail, Result};
 use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
 use clap::error::{Error, ErrorKind};
@@ -396,3 +396,18 @@ impl WasmtimeOptionValue for WasiNnGraph {
         })
     }
 }
+
+impl WasmtimeOptionValue for WasiRuntimeConfigVariable {
+    const VAL_HELP: &'static str = "=<name>=<val>";
+    fn parse(val: Option<&str>) -> Result<Self> {
+        let val = String::parse(val)?;
+        let mut parts = val.splitn(2, "=");
+        Ok(WasiRuntimeConfigVariable {
+            key: parts.next().unwrap().to_string(),
+            value: match parts.next() {
+                Some(part) => part.into(),
+                None => "".to_string(),
+            },
+        })
+    }
+}
diff --git a/crates/wasi-runtime-config/src/lib.rs b/crates/wasi-runtime-config/src/lib.rs
index bae199fb5..615e7b873 100644
--- a/crates/wasi-runtime-config/src/lib.rs
+++ b/crates/wasi-runtime-config/src/lib.rs
@@ -115,6 +115,13 @@ impl<'a> From<&'a WasiRuntimeConfigVariables> for WasiRuntimeConfig<'a> {
     }
 }
 
+impl<'a> WasiRuntimeConfig<'a> {
+    /// Create a new view into the `wasi-runtime-config` state.
+    pub fn new(vars: &'a WasiRuntimeConfigVariables) -> Self {
+        Self { vars }
+    }
+}
+
 impl generated::Host for WasiRuntimeConfig<'_> {
     fn get(&mut self, key: String) -> Result<Result<Option<String>, generated::ConfigError>> {
         Ok(Ok(self.vars.0.get(&key).map(|s| s.to_owned())))
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 3aa9a4b4f..1c1609599 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -25,6 +25,8 @@ use wasmtime_wasi_threads::WasiThreadsCtx;
 
 #[cfg(feature = "wasi-http")]
 use wasmtime_wasi_http::WasiHttpCtx;
+#[cfg(feature = "wasi-runtime-config")]
+use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
 
 fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
     let parts: Vec<&str> = s.splitn(2, '=').collect();
@@ -670,6 +672,38 @@ impl RunCommand {
             }
         }
 
+        if self.run.common.wasi.runtime_config == Some(true) {
+            #[cfg(not(feature = "wasi-runtime-config"))]
+            {
+                bail!("Cannot enable wasi-runtime-config when the binary is not compiled with this feature.");
+            }
+            #[cfg(all(feature = "wasi-runtime-config", feature = "component-model"))]
+            {
+                match linker {
+                    CliLinker::Core(_) => {
+                        bail!("Cannot enable wasi-runtime-config for core wasm modules");
+                    }
+                    CliLinker::Component(linker) => {
+                        let vars = WasiRuntimeConfigVariables::from_iter(
+                            self.run
+                                .common
+                                .wasi
+                                .runtime_config_var
+                                .iter()
+                                .map(|v| (v.key.clone(), v.value.clone())),
+                        );
+
+                        wasmtime_wasi_runtime_config::add_to_linker(linker, |h| {
+                            WasiRuntimeConfig::new(
+                                Arc::get_mut(h.wasi_runtime_config.as_mut().unwrap()).unwrap(),
+                            )
+                        })?;
+                        store.data_mut().wasi_runtime_config = Some(Arc::new(vars));
+                    }
+                }
+            }
+        }
+
         if self.run.common.wasi.threads == Some(true) {
             #[cfg(not(feature = "wasi-threads"))]
             {
@@ -814,6 +848,9 @@ struct Host {
     limits: StoreLimits,
     #[cfg(feature = "profiling")]
     guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
+
+    #[cfg(feature = "wasi-runtime-config")]
+    wasi_runtime_config: Option<Arc<WasiRuntimeConfigVariables>>,
 }
 
 impl Host {
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 949d856c2..e29a2f869 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -19,6 +19,8 @@ use wasmtime_wasi_http::{body::HyperOutgoingBody, WasiHttpCtx, WasiHttpView};
 
 #[cfg(feature = "wasi-nn")]
 use wasmtime_wasi_nn::wit::WasiNnCtx;
+#[cfg(feature = "wasi-runtime-config")]
+use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
 
 struct Host {
     table: wasmtime::component::ResourceTable,
@@ -29,6 +31,9 @@ struct Host {
 
     #[cfg(feature = "wasi-nn")]
     nn: Option<WasiNnCtx>,
+
+    #[cfg(feature = "wasi-runtime-config")]
+    wasi_runtime_config: Option<WasiRuntimeConfigVariables>,
 }
 
 impl WasiView for Host {
@@ -147,6 +152,8 @@ impl ServeCommand {
 
             #[cfg(feature = "wasi-nn")]
             nn: None,
+            #[cfg(feature = "wasi-runtime-config")]
+            wasi_runtime_config: None,
         };
 
         if self.run.common.wasi.nn == Some(true) {
@@ -165,6 +172,21 @@ impl ServeCommand {
             }
         }
 
+        if self.run.common.wasi.runtime_config == Some(true) {
+            #[cfg(feature = "wasi-runtime-config")]
+            {
+                let vars = WasiRuntimeConfigVariables::from_iter(
+                    self.run
+                        .common
+                        .wasi
+                        .runtime_config_var
+                        .iter()
+                        .map(|v| (v.key.clone(), v.value.clone())),
+                );
+                host.wasi_runtime_config.replace(vars);
+            }
+        }
+
         let mut store = Store::new(engine, host);
 
         if self.run.common.wasm.timeout.is_some() {
@@ -228,6 +250,19 @@ impl ServeCommand {
             }
         }
 
+        if self.run.common.wasi.runtime_config == Some(true) {
+            #[cfg(not(feature = "wasi-runtime-config"))]
+            {
+                bail!("support for wasi-runtime-config was disabled at compile time");
+            }
+            #[cfg(feature = "wasi-runtime-config")]
+            {
+                wasmtime_wasi_runtime_config::add_to_linker(linker, |h| {
+                    WasiRuntimeConfig::from(h.wasi_runtime_config.as_ref().unwrap())
+                })?;
+            }
+        }
+
         if self.run.common.wasi.threads == Some(true) {
             bail!("support for wasi-threads is not available with components");
         }
diff --git a/winch/codegen/src/isa/x64/regs.rs b/winch/codegen/src/isa/x64/regs.rs
index 69e796246..0118f8b97 100644
--- a/winch/codegen/src/isa/x64/regs.rs
+++ b/winch/codegen/src/isa/x64/regs.rs
@@ -99,18 +99,6 @@ pub(crate) fn scratch() -> Reg {
     r11()
 }
 
-/// This register is used as a scratch register, in the context of trampolines only,
-/// where we assume that callee-saved registers are given the correct handling
-/// according to the system ABI. r12 is chosen given that it's a callee-saved,
-/// non-argument register.
-///
-/// In the context of all other internal functions, this register is not excluded
-/// from register allocation, so no extra assumptions should be made regarding
-/// its availability.
-pub(crate) fn argv() -> Reg {
-    r12()
-}
-
 fn fpr(enc: u8) -> Reg {
     Reg::new(PReg::new(enc as usize, RegClass::Float))
 }


Test patch:
diff --git a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
index 2b53fae95..5dd3dd505 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs
@@ -1632,6 +1632,36 @@ fn test_x64_emit() {
         "4520FF",
         "andb    %r15b, %r15b, %r15b",
     ));
+    insns.push((
+        Inst::alu_rmi_r(
+            OperandSize::Size32,
+            AluRmiROpcode::Sbb,
+            RegMemImm::reg(r14),
+            w_r15,
+        ),
+        "4519F7",
+        "sbbl    %r15d, %r14d, %r15d",
+    ));
+    insns.push((
+        Inst::alu_rmi_r(
+            OperandSize::Size64,
+            AluRmiROpcode::Sbb,
+            RegMemImm::imm(0),
+            w_r15,
+        ),
+        "4983DF00",
+        "sbbq    %r15, $0, %r15",
+    ));
+    insns.push((
+        Inst::alu_rmi_r(
+            OperandSize::Size64,
+            AluRmiROpcode::Adc,
+            RegMemImm::imm(0),
+            w_r15,
+        ),
+        "4983D700",
+        "adcq    %r15, $0, %r15",
+    ));
 
     // ========================================================
     // AluRM
diff --git a/cranelift/filetests/filetests/egraph/cprop.clif b/cranelift/filetests/filetests/egraph/cprop.clif
index 70d026f41..f1ca491f4 100644
--- a/cranelift/filetests/filetests/egraph/cprop.clif
+++ b/cranelift/filetests/filetests/egraph/cprop.clif
@@ -344,6 +344,122 @@ block0:
 ; check: v4 = f16const -NaN
 ; check: return v4  ; v4 = -NaN
 
+function %f32_fadd() -> f32 {
+block0:
+    v1 = f32const 0x1.fp2
+    v2 = f32const 0x1.9p3
+    v3 = fadd v1, v2
+    return v3
+}
+
+; check: v4 = f32const 0x1.440000p4
+; check: return v4  ; v4 = 0x1.440000p4
+
+function %f32_fsub() -> f32 {
+block0:
+    v1 = f32const 0x1.fp2
+    v2 = f32const 0x1.9p3
+    v3 = fsub v1, v2
+    return v3
+}
+
+; check: v4 = f32const -0x1.300000p2
+; check: return v4  ; v4 = -0x1.300000p2
+
+function %f32_fmul() -> f32 {
+block0:
+    v1 = f32const 0x1.fp2
+    v2 = f32const 0x1.9p3
+    v3 = fmul v1, v2
+    return v3
+}
+
+; check: v4 = f32const 0x1.838000p6
+; check: return v4  ; v4 = 0x1.838000p6
+
+function %f32_fdiv() -> f32 {
+block0:
+    v1 = f32const 0x1.9p5
+    v2 = f32const 0x1.9p3
+    v3 = fdiv v1, v2
+    return v3
+}
+
+; check: v4 = f32const 0x1.000000p2
+; check: return v4  ; v4 = 0x1.000000p2
+
+function %f32_sqrt() -> f32 {
+block0:
+    v1 = f32const 0x1.9p4
+    v2 = sqrt v1
+    return v2
+}
+
+; check: v3 = f32const 0x1.400000p2
+; check: return v3  ; v3 = 0x1.400000p2
+
+function %f32_ceil() -> f32 {
+block0:
+    v1 = f32const -0x1.9p3
+    v2 = ceil v1
+    return v2
+}
+
+; check: v3 = f32const -0x1.800000p3
+; check: return v3  ; v3 = -0x1.800000p3
+
+function %f32_floor() -> f32 {
+block0:
+    v1 = f32const -0x1.9p3
+    v2 = floor v1
+    return v2
+}
+
+; check: v3 = f32const -0x1.a00000p3
+; check: return v3  ; v3 = -0x1.a00000p3
+
+function %f32_trunc() -> f32 {
+block0:
+    v1 = f32const 0x1.9p3
+    v2 = trunc v1
+    return v2
+}
+
+; check: v3 = f32const 0x1.800000p3
+; check: return v3  ; v3 = 0x1.800000p3
+
+function %f32_nearest() -> f32 {
+block0:
+    v1 = f32const 0x1.9p3
+    v2 = nearest v1
+    return v2
+}
+
+; check: v3 = f32const 0x1.800000p3
+; check: return v3  ; v3 = 0x1.800000p3
+
+function %f32_fmin() -> f32 {
+block0:
+    v1 = f32const 0x1.5p6
+    v2 = f32const 0x1.5p7
+    v3 = fmin v2, v1
+    return v3
+}
+
+; check: v4 = f32const 0x1.500000p6
+; check: return v4  ; v4 = 0x1.500000p6
+
+function %f32_fmax() -> f32 {
+block0:
+    v1 = f32const 0x1.5p6
+    v2 = f32const 0x1.5p7
+    v3 = fmax v2, v1
+    return v3
+}
+
+; check: v4 = f32const 0x1.500000p7
+; check: return v4  ; v4 = 0x1.500000p7
+
 function %f32_fneg() -> f32 {
 block0:
     v1 = f32const 0.0
@@ -375,6 +491,122 @@ block0:
 ; check: v4 = f32const -NaN
 ; check: return v4  ; v4 = -NaN
 
+function %f64_fadd() -> f64 {
+block0:
+    v1 = f64const 0x1.fp2
+    v2 = f64const 0x1.9p3
+    v3 = fadd v1, v2
+    return v3
+}
+
+; check: v4 = f64const 0x1.4400000000000p4
+; check: return v4  ; v4 = 0x1.4400000000000p4
+
+function %f64_fsub() -> f64 {
+block0:
+    v1 = f64const 0x1.fp2
+    v2 = f64const 0x1.9p3
+    v3 = fsub v1, v2
+    return v3
+}
+
+; check: v4 = f64const -0x1.3000000000000p2
+; check: return v4  ; v4 = -0x1.3000000000000p2
+
+function %f64_fmul() -> f64 {
+block0:
+    v1 = f64const 0x1.fp2
+    v2 = f64const 0x1.9p3
+    v3 = fmul v1, v2
+    return v3
+}
+
+; check: v4 = f64const 0x1.8380000000000p6
+; check: return v4  ; v4 = 0x1.8380000000000p6
+
+function %f64_fdiv() -> f64 {
+block0:
+    v1 = f64const 0x1.9p5
+    v2 = f64const 0x1.9p3
+    v3 = fdiv v1, v2
+    return v3
+}
+
+; check: v4 = f64const 0x1.0000000000000p2
+; check: return v4  ; v4 = 0x1.0000000000000p2
+
+function %f64_sqrt() -> f64 {
+block0:
+    v1 = f64const 0x1.9p4
+    v2 = sqrt v1
+    return v2
+}
+
+; check: v3 = f64const 0x1.4000000000000p2
+; check: return v3  ; v3 = 0x1.4000000000000p2
+
+function %f64_ceil() -> f64 {
+block0:
+    v1 = f64const 0x1.9p3
+    v2 = ceil v1
+    return v2
+}
+
+; check: v3 = f64const 0x1.a000000000000p3
+; check: return v3  ; v3 = 0x1.a000000000000p3
+
+function %f64_floor() -> f64 {
+block0:
+    v1 = f64const 0x1.9p3
+    v2 = floor v1
+    return v2
+}
+
+; check: v3 = f64const 0x1.8000000000000p3
+; check: return v3  ; v3 = 0x1.8000000000000p3
+
+function %f64_trunc() -> f64 {
+block0:
+    v1 = f64const 0x1.9p3
+    v2 = trunc v1
+    return v2
+}
+
+; check: v3 = f64const 0x1.8000000000000p3
+; check: return v3  ; v3 = 0x1.8000000000000p3
+
+function %f64_nearest() -> f64 {
+block0:
+    v1 = f64const 0x1.9p3
+    v2 = nearest v1
+    return v2
+}
+
+; check: v3 = f64const 0x1.8000000000000p3
+; check: return v3  ; v3 = 0x1.8000000000000p3
+
+function %f64_fmin() -> f64 {
+block0:
+    v1 = f64const -0x1.5p6
+    v2 = f64const -0x1.5p7
+    v3 = fmin v2, v1
+    return v3
+}
+
+; check: v4 = f64const -0x1.5000000000000p7
+; check: return v4  ; v4 = -0x1.5000000000000p7
+
+function %f64_fmax() -> f64 {
+block0:
+    v1 = f64const -0x1.5p6
+    v2 = f64const -0x1.5p7
+    v3 = fmax v2, v1
+    return v3
+}
+
+; check: v4 = f64const -0x1.5000000000000p6
+; check: return v4  ; v4 = -0x1.5000000000000p6
+
 function %f64_fneg() -> f64 {
 block0:
     v1 = f64const 0.0
diff --git a/crates/test-programs/src/bin/cli_serve_runtime_config.rs b/crates/test-programs/src/bin/cli_serve_runtime_config.rs
new file mode 100644
index 000000000..48b1027a9
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_serve_runtime_config.rs
@@ -0,0 +1,29 @@
+use test_programs::config::wasi::config::runtime;
+use test_programs::proxy;
+use test_programs::wasi::http::types::{
+    Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
+};
+
+struct T;
+
+proxy::export!(T);
+
+impl proxy::exports::wasi::http::incoming_handler::Guest for T {
+    fn handle(_: IncomingRequest, outparam: ResponseOutparam) {
+        let fields = Fields::new();
+        let resp = OutgoingResponse::new(fields);
+        let body = resp.body().expect("outgoing response");
+
+        ResponseOutparam::set(outparam, Ok(resp));
+
+        let out = body.write().expect("outgoing stream");
+        let config = runtime::get("hello").unwrap().unwrap();
+        out.blocking_write_and_flush(config.as_bytes())
+            .expect("writing response");
+
+        drop(out);
+        OutgoingBody::finish(body, None).expect("outgoing-body.finish");
+    }
+}
+
+fn main() {}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 41754789c..90265a772 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1853,6 +1853,39 @@ stderr [1] :: after empty
         run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?;
         Ok(())
     }
+
+    #[tokio::test]
+    async fn cli_serve_runtime_config() -> Result<()> {
+        let server = WasmtimeServe::new(CLI_SERVE_RUNTIME_CONFIG_COMPONENT, |cmd| {
+            cmd.arg("-Scli");
+            cmd.arg("-Sruntime-config");
+            cmd.arg("-Sruntime-config-var=hello=world");
+        })?;
+
+        let resp = server
+            .send_request(
+                hyper::Request::builder()
+                    .uri("http://localhost/")
+                    .body(String::new())
+                    .context("failed to make request")?,
+            )
+            .await?;
+
+        assert!(resp.status().is_success());
+        assert_eq!(resp.body(), "world");
+        Ok(())
+    }
+
+    #[test]
+    fn cli_runtime_config() -> Result<()> {
+        run_wasmtime(&[
+            "run",
+            "-Sruntime-config",
+            "-Sruntime-config-var=hello=world",
+            RUNTIME_CONFIG_GET_COMPONENT,
+        ])?;
+        Ok(())
+    }
 }
 
 #[test]