Repository: bytecodealliance/wasmtime
Pull request #8971 (2024-07-18 14:04:31)

winch: Improve scratch register handling
This commit doesn't introduce any new behavior. It's mostly a follow-up to https://github.com/bytecodealliance/wasmtime/pull/7977.

This commit tries to reduce the repetitive pattern used to obtain the scrtach register by introducing a macro similar to the existing `vmctx!` macro.

This commit also improves the macro definition by using fully qualified paths.

<!--
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
-->


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

cc @saulecabrera

<details>
This issue or pull request has been labeled: "winch"

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

* saulecabrera: winch

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

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

Patch:
diff --git a/.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/bitset/Cargo.toml b/cranelift/bitset/Cargo.toml
index 0731e450b..3401f6705 100644
--- a/cranelift/bitset/Cargo.toml
+++ b/cranelift/bitset/Cargo.toml
@@ -6,7 +6,6 @@ description = "Various bitset stuff for use inside Cranelift"
 license = "Apache-2.0 WITH LLVM-exception"
 documentation = "https://docs.rs/cranelift-bitset"
 repository = "https://github.com/bytecodealliance/wasmtime"
-readme = "README.md"
 edition.workspace = true
 
 [lints]
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-http/src/body.rs b/crates/wasi-http/src/body.rs
index 427b527ba..5f16f4239 100644
--- a/crates/wasi-http/src/body.rs
+++ b/crates/wasi-http/src/body.rs
@@ -23,6 +23,7 @@ pub type HyperIncomingBody = BoxBody<Bytes, types::ErrorCode>;
 pub type HyperOutgoingBody = BoxBody<Bytes, types::ErrorCode>;
 
 /// The concrete type behind a `was:http/types/incoming-body` resource.
+#[derive(Debug)]
 pub struct HostIncomingBody {
     body: IncomingBodyState,
     /// An optional worker task to keep alive while this body is being read.
@@ -72,6 +73,7 @@ impl HostIncomingBody {
 }
 
 /// Internal state of a [`HostIncomingBody`].
+#[derive(Debug)]
 enum IncomingBodyState {
     /// The body is stored here meaning that within `HostIncomingBody` the
     /// `take_stream` method can be called for example.
@@ -84,6 +86,7 @@ enum IncomingBodyState {
 }
 
 /// Small wrapper around [`HyperIncomingBody`] which adds a timeout to every frame.
+#[derive(Debug)]
 struct BodyWithTimeout {
     /// Underlying stream that frames are coming from.
     inner: HyperIncomingBody,
@@ -146,6 +149,7 @@ impl Body for BodyWithTimeout {
 
 /// Message sent when a `HostIncomingBodyStream` is done to the
 /// `HostFutureTrailers` state.
+#[derive(Debug)]
 enum StreamEnd {
     /// The body wasn't completely read and was dropped early. May still have
     /// trailers, but requires reading more frames.
@@ -158,6 +162,7 @@ enum StreamEnd {
 
 /// The concrete type behind the `wasi:io/streams/input-stream` resource returned
 /// by `wasi:http/types/incoming-body`'s `stream` method.
+#[derive(Debug)]
 pub struct HostIncomingBodyStream {
     state: IncomingBodyStreamState,
     buffer: Bytes,
@@ -209,6 +214,7 @@ impl HostIncomingBodyStream {
     }
 }
 
+#[derive(Debug)]
 enum IncomingBodyStreamState {
     /// The body is currently open for reading and present here.
     ///
@@ -293,6 +299,7 @@ impl Drop for HostIncomingBodyStream {
 }
 
 /// The concrete type behind a `wasi:http/types/future-trailers` resource.
+#[derive(Debug)]
 pub enum HostFutureTrailers {
     /// Trailers aren't here yet.
     ///
@@ -378,7 +385,7 @@ impl Subscribe for HostFutureTrailers {
     }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 struct WrittenState {
     expected: u64,
     written: Arc<std::sync::atomic::AtomicU64>,
@@ -540,6 +547,7 @@ impl HostOutgoingBody {
 }
 
 /// Message sent to end the `[HostOutgoingBody]` stream.
+#[derive(Debug)]
 enum FinishMessage {
     Finished,
     Trailers(hyper::HeaderMap),
@@ -566,6 +574,7 @@ impl StreamContext {
 }
 
 /// Provides a [`HostOutputStream`] impl from a [`tokio::sync::mpsc::Sender`].
+#[derive(Debug)]
 struct BodyWriteStream {
     context: StreamContext,
     writer: mpsc::Sender<Bytes>,
diff --git a/crates/wasi-http/src/io.rs b/crates/wasi-http/src/io.rs
index e8446c075..962bd58f2 100644
--- a/crates/wasi-http/src/io.rs
+++ b/crates/wasi-http/src/io.rs
@@ -8,6 +8,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
 
 /// A type that wraps any type implementing [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`]
 /// and itself implements [`hyper::rt::Read`] and [`hyper::rt::Write`].
+#[derive(Debug)]
 pub struct TokioIo<T> {
     inner: T,
 }
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index 4f22444d3..4821e65c2 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -21,6 +21,7 @@ use wasmtime::component::{Resource, ResourceTable};
 use wasmtime_wasi::{runtime::AbortOnDropJoinHandle, Subscribe};
 
 /// Capture the state necessary for use in the wasi-http API implementation.
+#[derive(Debug)]
 pub struct WasiHttpCtx {
     _priv: (),
 }
@@ -486,6 +487,7 @@ impl TryInto<http::Method> for types::Method {
 }
 
 /// The concrete type behind a `wasi:http/types/incoming-request` resource.
+#[derive(Debug)]
 pub struct HostIncomingRequest {
     pub(crate) parts: http::request::Parts,
     pub(crate) scheme: Scheme,
@@ -561,6 +563,7 @@ impl TryFrom<HostOutgoingResponse> for hyper::Response<HyperOutgoingBody> {
 }
 
 /// The concrete type behind a `wasi:http/types/outgoing-request` resource.
+#[derive(Debug)]
 pub struct HostOutgoingRequest {
     /// The method of the request.
     pub method: Method,
@@ -577,7 +580,7 @@ pub struct HostOutgoingRequest {
 }
 
 /// The concrete type behind a `wasi:http/types/request-options` resource.
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct HostRequestOptions {
     /// How long to wait for a connection to be established.
     pub connect_timeout: Option<std::time::Duration>,
@@ -588,6 +591,7 @@ pub struct HostRequestOptions {
 }
 
 /// The concrete type behind a `wasi:http/types/incoming-response` resource.
+#[derive(Debug)]
 pub struct HostIncomingResponse {
     /// The response status
     pub status: u16,
@@ -598,6 +602,7 @@ pub struct HostIncomingResponse {
 }
 
 /// The concrete type behind a `wasi:http/types/fields` resource.
+#[derive(Debug)]
 pub enum HostFields {
     /// A reference to the fields of a parent entry.
     Ref {
@@ -626,6 +631,7 @@ pub type FutureIncomingResponseHandle =
     AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponse, types::ErrorCode>>>;
 
 /// A response that is in the process of being received.
+#[derive(Debug)]
 pub struct IncomingResponse {
     /// The response itself.
     pub resp: hyper::Response<HyperIncomingBody>,
@@ -636,6 +642,7 @@ pub struct IncomingResponse {
 }
 
 /// The concrete type behind a `wasi:http/types/future-incoming-response` resource.
+#[derive(Debug)]
 pub enum HostFutureIncomingResponse {
     /// A pending response
     Pending(FutureIncomingResponseHandle),
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/crates/wasi-threads/Cargo.toml b/crates/wasi-threads/Cargo.toml
index 84b055385..467ce1e6f 100644
--- a/crates/wasi-threads/Cargo.toml
+++ b/crates/wasi-threads/Cargo.toml
@@ -5,7 +5,7 @@ authors.workspace = true
 description = "Wasmtime implementation of the wasi-threads API"
 documentation = "https://docs.rs/wasmtime-wasi-nn"
 license = "Apache-2.0 WITH LLVM-exception"
-categories = ["wasm", "parallelism", "threads"]
+categories = ["wasm"]
 keywords = ["webassembly", "wasm", "neural-network"]
 repository = "https://github.com/bytecodealliance/wasmtime"
 readme = "README.md"
diff --git a/crates/wasi/src/runtime.rs b/crates/wasi/src/runtime.rs
index b92509810..da41faa38 100644
--- a/crates/wasi/src/runtime.rs
+++ b/crates/wasi/src/runtime.rs
@@ -37,6 +37,7 @@ pub(crate) static RUNTIME: once_cell::sync::Lazy<tokio::runtime::Runtime> =
 ///
 /// This behavior makes it easier to tie a worker task to the lifetime of a Resource
 /// by keeping this handle owned by the Resource.
+#[derive(Debug)]
 pub struct AbortOnDropJoinHandle<T>(tokio::task::JoinHandle<T>);
 impl<T> Drop for AbortOnDropJoinHandle<T> {
     fn drop(&mut self) {
diff --git a/docs/stability-tiers.md b/docs/stability-tiers.md
index a038e296e..b294270ef 100644
--- a/docs/stability-tiers.md
+++ b/docs/stability-tiers.md
@@ -96,11 +96,13 @@ For explanations of what each tier means see below.
 | WebAssembly Proposal | [`gc`]                            | Complete implementation     |
 | WASI Proposal        | [`wasi-nn`]                       | More expansive CI testing   |
 | WASI Proposal        | [`wasi-threads`]                  | More CI, unstable proposal  |
+| WASI Proposal        | [`wasi-runtime-config`]           | unstable proposal           |
 | *misc*               | Non-Wasmtime Cranelift usage [^1] | CI testing, full-time maintainer |
 | *misc*               | DWARF debugging [^2]              | CI testing, full-time maintainer, improved quality |
 
 [`wasi-nn`]: https://github.com/WebAssembly/wasi-nn
 [`wasi-threads`]: https://github.com/WebAssembly/wasi-threads
+[`wasi-runtime-config`]: https://github.com/WebAssembly/wasi-runtime-config
 [`gc`]: https://github.com/WebAssembly/gc
 
 [^1]: This is intended to encompass features that Cranelift supports as a
@@ -159,7 +161,6 @@ features to figure out how best to implement them and at least move them to Tier
 * [WASI proposal: `wasi-message-queue`](https://github.com/WebAssembly/wasi-message-queue)
 * [WASI proposal: `wasi-parallel`](https://github.com/WebAssembly/wasi-parallel)
 * [WASI proposal: `wasi-pubsub`](https://github.com/WebAssembly/wasi-pubsub)
-* [WASI proposal: `wasi-runtime-config`](https://github.com/WebAssembly/wasi-runtime-config)
 * [WASI proposal: `wasi-sql`](https://github.com/WebAssembly/wasi-sql)
 * [WASI proposal: `wasi-url`](https://github.com/WebAssembly/wasi-url)
 
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/abi/mod.rs b/winch/codegen/src/abi/mod.rs
index b5f46ee63..e591f7e8f 100644
--- a/winch/codegen/src/abi/mod.rs
+++ b/winch/codegen/src/abi/mod.rs
@@ -67,10 +67,22 @@ pub(super) enum ParamsOrReturns {
 /// Macro to get the pinned register holding the [VMContext].
 macro_rules! vmctx {
     ($m:ident) => {
-        <$m::ABI as ABI>::vmctx_reg()
+        <$m::ABI as $crate::abi::ABI>::vmctx_reg()
     };
 }
 
+/// Macro to get the designated general purpose scratch register or the
+/// designated scratch register for the given type.
+macro_rules! scratch {
+    ($m:ident) => {
+        <$m::ABI as $crate::abi::ABI>::scratch_reg()
+    };
+    ($m:ident, $wasm_type:expr) => {
+        <$m::ABI as $crate::abi::ABI>::scratch_for($wasm_type)
+    };
+}
+
+pub(crate) use scratch;
 pub(crate) use vmctx;
 
 /// Constructs an [ABISig] using Winch's ABI.
diff --git a/winch/codegen/src/codegen/bounds.rs b/winch/codegen/src/codegen/bounds.rs
index c1fb6d395..0411a031e 100644
--- a/winch/codegen/src/codegen/bounds.rs
+++ b/winch/codegen/src/codegen/bounds.rs
@@ -3,7 +3,7 @@
 //! recommended when working on this area of Winch.
 use super::env::{HeapData, HeapStyle};
 use crate::{
-    abi::{vmctx, ABI},
+    abi::{scratch, vmctx},
     codegen::CodeGenContext,
     isa::reg::Reg,
     masm::{IntCmpKind, MacroAssembler, OperandSize, RegImm, TrapCode},
@@ -96,7 +96,7 @@ where
             masm.mov(RegImm::i64(max_size as i64), dst, ptr_size)
         }
         (_, HeapStyle::Dynamic) => {
-            let scratch = <M::ABI as ABI>::scratch_reg();
+            let scratch = scratch!(M);
             let base = if let Some(offset) = heap.import_from {
                 let addr = masm.address_at_vmctx(offset);
                 masm.load_ptr(addr, scratch);
@@ -199,7 +199,7 @@ pub(crate) fn load_heap_addr_unchecked<M>(
     let base = if let Some(offset) = heap.import_from {
         // If the WebAssembly memory is imported, load the address into
         // the scratch register.
-        let scratch = <M::ABI as ABI>::scratch_reg();
+        let scratch = scratch!(M);
         masm.load_ptr(masm.address_at_vmctx(offset), scratch);
         scratch
     } else {
diff --git a/winch/codegen/src/codegen/call.rs b/winch/codegen/src/codegen/call.rs
index 84680e897..50d501db9 100644
--- a/winch/codegen/src/codegen/call.rs
+++ b/winch/codegen/src/codegen/call.rs
@@ -57,7 +57,7 @@
 //! └──────────────────────────────────────────────────┘ ------> Stack pointer when emitting the call
 
 use crate::{
-    abi::{vmctx, ABIOperand, ABISig, RetArea, ABI},
+    abi::{scratch, vmctx, ABIOperand, ABISig, RetArea},
     codegen::{BuiltinFunction, BuiltinType, Callee, CodeGenContext},
     masm::{
         CalleeKind, ContextArgs, MacroAssembler, MemMoveDirection, OperandSize, SPOffset,
@@ -299,7 +299,7 @@ impl FnCall {
                 &ABIOperand::Stack { ty, offset, .. } => {
                     let addr = masm.address_at_sp(SPOffset::from_u32(offset));
                     let size: OperandSize = ty.into();
-                    let scratch = <M::ABI as ABI>::scratch_for(&ty);
+                    let scratch = scratch!(M, &ty);
                     context.move_val_to_reg(val, scratch, masm);
                     masm.store(scratch.into(), addr, size);
                 }
@@ -319,7 +319,7 @@ impl FnCall {
                     let slot = masm.address_at_sp(SPOffset::from_u32(offset));
                     // Don't rely on `ABI::scratch_for` as we always use
                     // an int register as the return pointer.
-                    let scratch = <M::ABI as ABI>::scratch_reg();
+                    let scratch = scratch!(M);
                     masm.load_addr(addr, scratch, ty.into());
                     masm.store(scratch.into(), slot, ty.into());
                 }
diff --git a/winch/codegen/src/codegen/context.rs b/winch/codegen/src/codegen/context.rs
index f9a3ead31..49804899f 100644
--- a/winch/codegen/src/codegen/context.rs
+++ b/winch/codegen/src/codegen/context.rs
@@ -2,7 +2,7 @@ use wasmtime_environ::{VMOffsets, WasmHeapType, WasmValType};
 
 use super::ControlStackFrame;
 use crate::{
-    abi::{vmctx, ABIOperand, ABIResults, RetArea, ABI},
+    abi::{scratch, vmctx, ABIOperand, ABIResults, RetArea},
     frame::Frame,
     isa::reg::RegClass,
     masm::{MacroAssembler, OperandSize, RegImm, SPOffset, ShiftKind, StackSlot},
@@ -185,13 +185,13 @@ impl<'a> CodeGenContext<'a> {
             Val::F64(v) => masm.store(RegImm::f64(v.bits()), addr, size),
             Val::Local(local) => {
                 let slot = self.frame.get_wasm_local(local.index);
-                let scratch = <M::ABI as ABI>::scratch_reg();
+                let scratch = scratch!(M);
                 let local_addr = masm.local_address(&slot);
                 masm.load(local_addr, scratch, size);
                 masm.store(scratch.into(), addr, size);
             }
             Val::Memory(_) => {
-                let scratch = <M::ABI as ABI>::scratch_reg();
+                let scratch = scratch!(M);
                 masm.pop(scratch, size);
                 masm.store(scratch.into(), addr, size);
             }
@@ -576,7 +576,7 @@ impl<'a> CodeGenContext<'a> {
             Val::Local(local) => {
                 let slot = frame.get_wasm_local(local.index);
                 let addr = masm.local_address(&slot);
-                let scratch = <M::ABI as ABI>::scratch_for(&slot.ty);
+                let scratch = scratch!(M, &slot.ty);
                 masm.load(addr, scratch, slot.ty.into());
                 let stack_slot = masm.push(scratch, slot.ty.into());
                 *v = Val::mem(slot.ty, stack_slot);
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 8ab22e69a..76e85c4ca 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -1,5 +1,5 @@
 use crate::{
-    abi::{vmctx, ABIOperand, ABISig, RetArea, ABI},
+    abi::{scratch, vmctx, ABIOperand, ABISig, RetArea},
     codegen::BlockSig,
     isa::reg::Reg,
     masm::{
@@ -329,7 +329,7 @@ where
             .checked_mul(sig_index_bytes.into())
             .unwrap();
         let signatures_base_offset = self.env.vmoffsets.ptr.vmctx_type_ids_array();
-        let scratch = <M::ABI as ABI>::scratch_reg();
+        let scratch = scratch!(M);
         let funcref_sig_offset = self.env.vmoffsets.ptr.vm_func_ref_type_index();
 
         // Load the signatures address into the scratch register.
@@ -443,7 +443,7 @@ where
 
         let addr = if data.imported {
             let global_base = self.masm.address_at_reg(vmctx!(M), data.offset);
-            let scratch = <M::ABI as ABI>::scratch_reg();
+            let scratch = scratch!(M);
             self.masm.load_ptr(global_base, scratch);
             self.masm.address_at_reg(scratch, 0)
         } else {
@@ -754,7 +754,7 @@ where
         base: Reg,
         table_data: &TableData,
     ) -> M::Address {
-        let scratch = <M::ABI as ABI>::scratch_reg();
+        let scratch = scratch!(M);
         let bound = self.context.any_gpr(self.masm);
         let tmp = self.context.any_gpr(self.masm);
         let ptr_size: OperandSize = self.env.ptr_type().into();
@@ -811,7 +811,7 @@ where
 
     /// Retrieves the size of the table, pushing the result to the value stack.
     pub fn emit_compute_table_size(&mut self, table_data: &TableData) {
-        let scratch = <M::ABI as ABI>::scratch_reg();
+        let scratch = scratch!(M);
         let size = self.context.any_gpr(self.masm);
         let ptr_size: OperandSize = self.env.ptr_type().into();
 
@@ -834,7 +834,7 @@ where
     /// Retrieves the size of the memory, pushing the result to the value stack.
     pub fn emit_compute_memory_size(&mut self, heap_data: &HeapData) {
         let size_reg = self.context.any_gpr(self.masm);
-        let scratch = <M::ABI as ABI>::scratch_reg();
+        let scratch = scratch!(M);
 
         let base = if let Some(offset) = heap_data.import_from {
             self.masm
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))
 }
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index eeb73b132..7f7f7d0ed 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -1,4 +1,4 @@
-use crate::abi::{self, align_to, LocalSlot};
+use crate::abi::{self, align_to, scratch, LocalSlot};
 use crate::codegen::{CodeGenContext, FuncEnv};
 use crate::isa::reg::Reg;
 use cranelift_codegen::{
@@ -592,7 +592,7 @@ pub(crate) trait MacroAssembler {
         debug_assert!(bytes % 4 == 0);
         let mut remaining = bytes;
         let word_bytes = <Self::ABI as abi::ABI>::word_bytes();
-        let scratch = <Self::ABI as abi::ABI>::scratch_reg();
+        let scratch = scratch!(Self);
 
         let mut dst_offs = dst.as_u32() - bytes;
         let mut src_offs = src.as_u32() - bytes;
@@ -869,7 +869,7 @@ pub(crate) trait MacroAssembler {
             // Add an upper bound to this generation;
             // given a considerably large amount of slots
             // this will be inefficient.
-            let zero = <Self::ABI as abi::ABI>::scratch_reg();
+            let zero = scratch!(Self);
             self.zero(zero);
             let zero = RegImm::reg(zero);
 


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]