Repository: bytecodealliance/wasmtime
Pull request #7226 (2023-10-12 15:53:49)

c-api/cmake: Support custom build options
This can be useful if you want change to set the features wasmtime
builds with in CMake

rockwotj: Our use case we only want to turn on the async feature and disable others, so we can configure like so (semicolons are lists and prevent quoting amazingly):

```bash
cmake .. -DWASMTIME_USER_CARGO_BUILD_OPTIONS='--no-default-features;--features=async'
```
github-actions[bot]: #### Subscribe to Label Action

cc @peterhuene

<details>
This issue or pull request has been labeled: "wasmtime:c-api"

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

* peterhuene: wasmtime:c-api

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

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

Patch:
diff --git a/cranelift/codegen/src/ir/memtype.rs b/cranelift/codegen/src/ir/memtype.rs
index 04def7cf7..711e761ab 100644
--- a/cranelift/codegen/src/ir/memtype.rs
+++ b/cranelift/codegen/src/ir/memtype.rs
@@ -159,6 +159,16 @@ pub struct MemoryTypeField {
     pub readonly: bool,
 }
 
+impl MemoryTypeField {
+    /// Get the fact, if any, on a field. Fills in a default inferred
+    /// fact based on the type if no explicit fact is present.
+    pub fn fact(&self) -> Option<&Fact> {
+        self.fact
+            .as_ref()
+            .or_else(|| Fact::infer_from_type(self.ty))
+    }
+}
+
 impl MemoryTypeData {
     /// Provide the static size of this type, if known.
     ///
diff --git a/cranelift/codegen/src/ir/pcc.rs b/cranelift/codegen/src/ir/pcc.rs
index fc98bab18..d1fcc2ad1 100644
--- a/cranelift/codegen/src/ir/pcc.rs
+++ b/cranelift/codegen/src/ir/pcc.rs
@@ -46,20 +46,40 @@
 //! `FactContext::add()` and friends to forward-propagate facts.
 //!
 //! TODO:
-//! - Check blockparams' preds against blockparams' facts.
+//!
+//! Completeness:
 //! - Propagate facts through optimization (egraph layer).
 //! - Generate facts in cranelift-wasm frontend when lowering memory ops.
-//! - Implement richer "points-to" facts that describe the pointed-to
-//!   memory, so the loaded values can also have facts.
 //! - Support bounds-checking-type operations for dynamic memories and
 //!   tables.
+//!
+//! Generality:
+//! - facts on outputs (in func signature)?
 //! - Implement checking at the CLIF level as well.
 //! - Check instructions that can trap as well?
+//!
+//! Nicer errors:
+//! - attach instruction index or some other identifier to errors
+//!
+//! Refactoring:
+//! - avoid the "default fact" infra everywhere we fetch facts,
+//!   instead doing it in the subsume check (and take the type with
+//!   subsume)?
+//!
+//! Text format cleanup:
+//! - make the bitwidth on `max` facts optional in the CLIF text
+//!   format?
+//! - make offset in `mem` fact optional in the text format?
+//!
+//! Bikeshed colors (syntax):
+//! - Put fact bang-annotations after types?
+//!   `v0: i64 ! fact(..)` vs. `v0 ! fact(..): i64`
 
 use crate::ir;
+use crate::ir::types::*;
 use crate::isa::TargetIsa;
-use crate::machinst::{InsnIndex, LowerBackend, MachInst, VCode};
-use cranelift_entity::PrimaryMap;
+use crate::machinst::{BlockIndex, LowerBackend, MachInst, VCode};
+use regalloc2::Function as _;
 use std::fmt;
 
 #[cfg(feature = "enable-serde")]
@@ -81,6 +101,9 @@ pub enum PccError {
     /// A derivation of an output fact is unsupported (incorrect or
     /// not derivable).
     UnsupportedFact,
+    /// A block parameter claims a fact that one of its predecessors
+    /// does not support.
+    UnsupportedBlockparam,
     /// A memory access is out of bounds.
     OutOfBounds,
     /// Proof-carrying-code checking is not implemented for a
@@ -90,6 +113,15 @@ pub enum PccError {
     /// particular instruction that instruction-selection chose. This
     /// is an internal compiler error.
     UnimplementedInst,
+    /// Access to an invalid or undefined field offset in a struct.
+    InvalidFieldOffset,
+    /// Access to a field via the wrong type.
+    BadFieldType,
+    /// Store to a read-only field.
+    WriteToReadOnlyField,
+    /// Store of data to a field with a fact that does not subsume the
+    /// field's fact.
+    InvalidStoredFact,
 }
 
 /// A fact on a value.
@@ -130,6 +162,37 @@ impl fmt::Display for Fact {
     }
 }
 
+impl Fact {
+    /// Try to infer a minimal fact for a value of the given IR type.
+    pub fn infer_from_type(ty: ir::Type) -> Option<&'static Self> {
+        static FACTS: [Fact; 4] = [
+            Fact::ValueMax {
+                bit_width: 8,
+                max: u8::MAX as u64,
+            },
+            Fact::ValueMax {
+                bit_width: 16,
+                max: u16::MAX as u64,
+            },
+            Fact::ValueMax {
+                bit_width: 32,
+                max: u32::MAX as u64,
+            },
+            Fact::ValueMax {
+                bit_width: 64,
+                max: u64::MAX,
+            },
+        ];
+        match ty {
+            I8 => Some(&FACTS[0]),
+            I16 => Some(&FACTS[1]),
+            I32 => Some(&FACTS[2]),
+            I64 => Some(&FACTS[3]),
+            _ => None,
+        }
+    }
+}
+
 macro_rules! ensure {
     ( $condition:expr, $err:tt $(,)? ) => {
         if !$condition {
@@ -148,18 +211,15 @@ macro_rules! bail {
 /// context carries environment/global properties, such as the machine
 /// pointer width.
 pub struct FactContext<'a> {
-    memory_types: &'a PrimaryMap<ir::MemoryType, ir::MemoryTypeData>,
+    function: &'a ir::Function,
     pointer_width: u16,
 }
 
 impl<'a> FactContext<'a> {
     /// Create a new "fact context" in which to evaluate facts.
-    pub fn new(
-        memory_types: &'a PrimaryMap<ir::MemoryType, ir::MemoryTypeData>,
-        pointer_width: u16,
-    ) -> Self {
+    pub fn new(function: &'a ir::Function, pointer_width: u16) -> Self {
         FactContext {
-            memory_types,
+            function,
             pointer_width,
         }
     }
@@ -170,6 +230,14 @@ impl<'a> FactContext<'a> {
             // Reflexivity.
             (l, r) if l == r => true,
 
+            // Any value on the LHS subsumes a minimal (always-true)
+            // fact about the max value of a given bitwidth on the
+            // RHS: e.g., no matter the value, the bottom 8 bits will
+            // always be <= 255.
+            (_, Fact::ValueMax { bit_width, max }) if *max == max_value_for_width(*bit_width) => {
+                true
+            }
+
             (
                 Fact::ValueMax {
                     bit_width: bw_lhs,
@@ -195,6 +263,18 @@ impl<'a> FactContext<'a> {
         }
     }
 
+    /// Computes whether the optional fact `lhs` subsumes (implies)
+    /// the optional fact `lhs`. A `None` never subsumes any fact, and
+    /// is always subsumed by any fact at all (or no fact).
+    pub fn subsumes_fact_optionals(&self, lhs: Option<&Fact>, rhs: Option<&Fact>) -> bool {
+        match (lhs, rhs) {
+            (None, None) => true,
+            (Some(_), None) => true,
+            (None, Some(_)) => false,
+            (Some(lhs), Some(rhs)) => self.subsumes(lhs, rhs),
+        }
+    }
+
     /// Computes whatever fact can be known about the sum of two
     /// values with attached facts. The add is performed to the given
     /// bit-width. Note that this is distinct from the machine or
@@ -213,6 +293,7 @@ impl<'a> FactContext<'a> {
                 },
             ) if bw_lhs == bw_rhs && add_width >= *bw_lhs => {
                 let computed_max = lhs.checked_add(*rhs)?;
+                let computed_max = std::cmp::min(max_value_for_width(add_width), computed_max);
                 Some(Fact::ValueMax {
                     bit_width: *bw_lhs,
                     max: computed_max,
@@ -352,7 +433,10 @@ impl<'a> FactContext<'a> {
 
     /// Check that accessing memory via a pointer with this fact, with
     /// a memory access of the given size, is valid.
-    pub fn check_address(&self, fact: &Fact, size: u32) -> PccResult<()> {
+    ///
+    /// If valid, returns the memory type and offset into that type
+    /// that this address accesses.
+    fn check_address(&self, fact: &Fact, size: u32) -> PccResult<(ir::MemoryType, i64)> {
         match fact {
             Fact::Mem { ty, offset } => {
                 let end_offset: i64 = offset
@@ -360,17 +444,70 @@ impl<'a> FactContext<'a> {
                     .ok_or(PccError::Overflow)?;
                 let end_offset: u64 =
                     u64::try_from(end_offset).map_err(|_| PccError::OutOfBounds)?;
-                match &self.memory_types[*ty] {
+                match &self.function.memory_types[*ty] {
                     ir::MemoryTypeData::Struct { size, .. }
                     | ir::MemoryTypeData::Memory { size } => {
                         ensure!(end_offset <= *size, OutOfBounds)
                     }
                     ir::MemoryTypeData::Empty => bail!(OutOfBounds),
                 }
+                Ok((*ty, *offset))
             }
             _ => bail!(OutOfBounds),
         }
+    }
+
+    /// Get the access struct field, if any, by a pointer with the
+    /// given fact and an access of the given type.
+    pub fn struct_field<'b>(
+        &'b self,
+        fact: &Fact,
+        access_ty: ir::Type,
+    ) -> PccResult<Option<&'b ir::MemoryTypeField>> {
+        let (ty, offset) = self.check_address(fact, access_ty.bytes())?;
+        let offset =
+            u64::try_from(offset).expect("valid access address cannot have a negative offset");
 
+        if let ir::MemoryTypeData::Struct { fields, .. } = &self.function.memory_types[ty] {
+            let field = fields
+                .iter()
+                .find(|field| field.offset == offset)
+                .ok_or(PccError::InvalidFieldOffset)?;
+            if field.ty != access_ty {
+                bail!(BadFieldType);
+            }
+            Ok(Some(field))
+        } else {
+            // Access to valid memory, but not a struct: no facts can be attached to the result.
+            Ok(None)
+        }
+    }
+
+    /// Check a load, and determine what fact, if any, the result of the load might have.
+    pub fn load<'b>(&'b self, fact: &Fact, access_ty: ir::Type) -> PccResult<Option<&'b Fact>> {
+        Ok(self
+            .struct_field(fact, access_ty)?
+            .and_then(|field| field.fact())
+            .or_else(|| Fact::infer_from_type(access_ty)))
+    }
+
+    /// Check a store.
+    pub fn store(
+        &self,
+        fact: &Fact,
+        access_ty: ir::Type,
+        data_fact: Option<&Fact>,
+    ) -> PccResult<()> {
+        if let Some(field) = self.struct_field(fact, access_ty)? {
+            // If it's a read-only field, disallow.
+            if field.readonly {
+                bail!(WriteToReadOnlyField);
+            }
+            // Check that the fact on the stored data subsumes the field's fact.
+            if !self.subsumes_fact_optionals(data_fact, field.fact()) {
+                bail!(InvalidStoredFact);
+            }
+        }
         Ok(())
     }
 }
@@ -391,15 +528,39 @@ pub fn check_vcode_facts<B: LowerBackend + TargetIsa>(
     vcode: &VCode<B::MInst>,
     backend: &B,
 ) -> PccResult<()> {
-    for inst in 0..vcode.num_insts() {
-        let inst = InsnIndex::new(inst);
-        if vcode.inst_defines_facts(inst) || vcode[inst].is_mem_access() {
-            // This instruction defines a register with a new fact, or
-            // has some side-effect we want to be careful to
-            // verify. We'll call into the backend to validate this
-            // fact with respect to the instruction and the input
-            // facts.
-            backend.check_fact(&vcode[inst], f, vcode)?;
+    let ctx = FactContext::new(f, backend.triple().pointer_width().unwrap().bits().into());
+
+    // Check that individual instructions are valid according to input
+    // facts, and support the stated output facts.
+    for block in 0..vcode.num_blocks() {
+        let block = BlockIndex::new(block);
+        for inst in vcode.block_insns(block).iter() {
+            if vcode.inst_defines_facts(inst) || vcode[inst].is_mem_access() {
+                // This instruction defines a register with a new fact, or
+                // has some side-effect we want to be careful to
+                // verify. We'll call into the backend to validate this
+                // fact with respect to the instruction and the input
+                // facts.
+                backend.check_fact(&ctx, vcode, &vcode[inst])?;
+            }
+
+            // If this is a branch, check that all block arguments subsume
+            // the assumed facts on the blockparams of successors.
+            if vcode.is_branch(inst) {
+                for (succ_idx, succ) in vcode.block_succs(block).iter().enumerate() {
+                    for (arg, param) in vcode
+                        .branch_blockparams(block, inst, succ_idx)
+                        .iter()
+                        .zip(vcode.block_params(*succ).iter())
+                    {
+                        let arg_fact = vcode.vreg_fact(*arg);
+                        let param_fact = vcode.vreg_fact(*param);
+                        if !ctx.subsumes_fact_optionals(arg_fact, param_fact) {
+                            return Err(PccError::UnsupportedBlockparam);
+                        }
+                    }
+                }
+            }
         }
     }
     Ok(())
diff --git a/cranelift/codegen/src/isa/aarch64/inst/args.rs b/cranelift/codegen/src/isa/aarch64/inst/args.rs
index 9abb50150..c6394e659 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/args.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/args.rs
@@ -647,6 +647,17 @@ impl ScalarSize {
             ScalarSize::Size128 => ScalarSize::Size64,
         }
     }
+
+    /// Return a type with the same size as this scalar.
+    pub fn ty(&self) -> Type {
+        match self {
+            ScalarSize::Size8 => I8,
+            ScalarSize::Size16 => I16,
+            ScalarSize::Size32 => I32,
+            ScalarSize::Size64 => I64,
+            ScalarSize::Size128 => I128,
+        }
+    }
 }
 
 /// Type used to communicate the size of a vector operand.
diff --git a/cranelift/codegen/src/isa/aarch64/lower.rs b/cranelift/codegen/src/isa/aarch64/lower.rs
index f3bbfd1e1..9a85e8fa1 100644
--- a/cranelift/codegen/src/isa/aarch64/lower.rs
+++ b/cranelift/codegen/src/isa/aarch64/lower.rs
@@ -8,9 +8,9 @@
 //! - Floating-point immediates (FIMM instruction).
 
 use crate::ir::condcodes::{FloatCC, IntCC};
-use crate::ir::pcc::PccResult;
+use crate::ir::pcc::{FactContext, PccResult};
 use crate::ir::Inst as IRInst;
-use crate::ir::{Function, Opcode, Value};
+use crate::ir::{Opcode, Value};
 use crate::isa::aarch64::inst::*;
 use crate::isa::aarch64::pcc;
 use crate::isa::aarch64::AArch64Backend;
@@ -133,10 +133,10 @@ impl LowerBackend for AArch64Backend {
 
     fn check_fact(
         &self,
-        inst: &Self::MInst,
-        function: &Function,
+        ctx: &FactContext<'_>,
         vcode: &VCode<Self::MInst>,
+        inst: &Self::MInst,
     ) -> PccResult<()> {
-        pcc::check(inst, function, vcode)
+        pcc::check(ctx, vcode, inst)
     }
 }
diff --git a/cranelift/codegen/src/isa/aarch64/pcc.rs b/cranelift/codegen/src/isa/aarch64/pcc.rs
index 81d330884..dea2515b3 100644
--- a/cranelift/codegen/src/isa/aarch64/pcc.rs
+++ b/cranelift/codegen/src/isa/aarch64/pcc.rs
@@ -1,7 +1,8 @@
 //! Proof-carrying code checking for AArch64 VCode.
 
 use crate::ir::pcc::*;
-use crate::ir::{Function, MemFlags};
+use crate::ir::types::*;
+use crate::ir::MemFlags;
 use crate::isa::aarch64::inst::args::{PairAMode, ShiftOp};
 use crate::isa::aarch64::inst::ALUOp;
 use crate::isa::aarch64::inst::Inst;
@@ -68,10 +69,7 @@ fn check_output<F: Fn() -> PccResult<Fact>>(
     }
 }
 
-pub(crate) fn check(inst: &Inst, function: &Function, vcode: &VCode<Inst>) -> PccResult<()> {
-    // Create a new fact context with the machine's pointer width.
-    let ctx = FactContext::new(&function.memory_types, 64);
-
+pub(crate) fn check(ctx: &FactContext, vcode: &VCode<Inst>, inst: &Inst) -> PccResult<()> {
     trace!("Checking facts on inst: {:?}", inst);
 
     match inst {
@@ -81,60 +79,50 @@ pub(crate) fn check(inst: &Inst, function: &Function, vcode: &VCode<Inst>) -> Pc
             // facts given to us in the CLIF should still be true.
             Ok(())
         }
-        Inst::ULoad8 { mem, flags, .. } | Inst::SLoad8 { mem, flags, .. } => {
-            check_addr(&ctx, *flags, mem, vcode, 1)
+        Inst::ULoad8 { rd, mem, flags } | Inst::SLoad8 { rd, mem, flags } => {
+            check_load(&ctx, Some(rd.to_reg()), *flags, mem, vcode, I8)
+        }
+        Inst::ULoad16 { rd, mem, flags } | Inst::SLoad16 { rd, mem, flags } => {
+            check_load(&ctx, Some(rd.to_reg()), *flags, mem, vcode, I16)
         }
-        Inst::ULoad16 { mem, flags, .. } | Inst::SLoad16 { mem, flags, .. } => {
-            check_addr(&ctx, *flags, mem, vcode, 2)
+        Inst::ULoad32 { rd, mem, flags } | Inst::SLoad32 { rd, mem, flags } => {
+            check_load(&ctx, Some(rd.to_reg()), *flags, mem, vcode, I32)
         }
-        Inst::ULoad32 { mem, flags, .. } | Inst::SLoad32 { mem, flags, .. } => {
-            check_addr(&ctx, *flags, mem, vcode, 4)
+        Inst::ULoad64 { rd, mem, flags } => {
+            check_load(&ctx, Some(rd.to_reg()), *flags, mem, vcode, I64)
         }
-        Inst::ULoad64 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 8),
-        Inst::FpuLoad32 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 4),
-        Inst::FpuLoad64 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 8),
-        Inst::FpuLoad128 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 16),
-        Inst::LoadP64 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 16),
-        Inst::FpuLoadP64 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 16),
-        Inst::FpuLoadP128 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 32),
-        Inst::VecLoadReplicate { rn, flags, .. } => check_scalar_addr(&ctx, *flags, *rn, vcode, 16),
+        Inst::FpuLoad32 { mem, flags, .. } => check_load(&ctx, None, *flags, mem, vcode, F32),
+        Inst::FpuLoad64 { mem, flags, .. } => check_load(&ctx, None, *flags, mem, vcode, F64),
+        Inst::FpuLoad128 { mem, flags, .. } => check_load(&ctx, None, *flags, mem, vcode, I8X16),
+        Inst::LoadP64 { mem, flags, .. } => check_load_pair(&ctx, *flags, mem, vcode, 16),
+        Inst::FpuLoadP64 { mem, flags, .. } => check_load_pair(&ctx, *flags, mem, vcode, 16),
+        Inst::FpuLoadP128 { mem, flags, .. } => check_load_pair(&ctx, *flags, mem, vcode, 32),
+        Inst::VecLoadReplicate {
+            rn, flags, size, ..
+        } => check_load_addr(&ctx, *flags, *rn, vcode, size.lane_size().ty()),
         Inst::LoadAcquire {
             access_ty,
             rn,
             flags,
             ..
-        } => check_scalar_addr(
-            &ctx,
-            *flags,
-            *rn,
-            vcode,
-            u8::try_from(access_ty.bytes()).unwrap(),
-        ),
+        } => check_load_addr(&ctx, *flags, *rn, vcode, *access_ty),
 
-        // TODO: stores: once we have memcaps, check that we aren't
-        // overwriting a field that has a pointee type.
-        Inst::Store8 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 1),
-        Inst::Store16 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 2),
-        Inst::Store32 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 4),
-        Inst::Store64 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 8),
-        Inst::FpuStore32 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 4),
-        Inst::FpuStore64 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 8),
-        Inst::FpuStore128 { mem, flags, .. } => check_addr(&ctx, *flags, mem, vcode, 16),
-        Inst::StoreP64 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 16),
-        Inst::FpuStoreP64 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 16),
-        Inst::FpuStoreP128 { mem, flags, .. } => check_addr_pair(&ctx, *flags, mem, vcode, 32),
+        Inst::Store8 { rd, mem, flags } => check_store(&ctx, Some(*rd), *flags, mem, vcode, I8),
+        Inst::Store16 { rd, mem, flags } => check_store(&ctx, Some(*rd), *flags, mem, vcode, I16),
+        Inst::Store32 { rd, mem, flags } => check_store(&ctx, Some(*rd), *flags, mem, vcode, I32),
+        Inst::Store64 { rd, mem, flags } => check_store(&ctx, Some(*rd), *flags, mem, vcode, I64),
+        Inst::FpuStore32 { mem, flags, .. } => check_store(&ctx, None, *flags, mem, vcode, F32),
+        Inst::FpuStore64 { mem, flags, .. } => check_store(&ctx, None, *flags, mem, vcode, F64),
+        Inst::FpuStore128 { mem, flags, .. } => check_store(&ctx, None, *flags, mem, vcode, I8X16),
+        Inst::StoreP64 { mem, flags, .. } => check_store_pair(&ctx, *flags, mem, vcode, 16),
+        Inst::FpuStoreP64 { mem, flags, .. } => check_store_pair(&ctx, *flags, mem, vcode, 16),
+        Inst::FpuStoreP128 { mem, flags, .. } => check_store_pair(&ctx, *flags, mem, vcode, 32),
         Inst::StoreRelease {
             access_ty,
             rn,
             flags,
             ..
-        } => check_scalar_addr(
-            &ctx,
-            *flags,
-            *rn,
-            vcode,
-            u8::try_from(access_ty.bytes()).unwrap(),
-        ),
+        } => check_store_addr(&ctx, *flags, *rn, vcode, *access_ty),
 
         Inst::AluRRR {
             alu_op: ALUOp::Add,
@@ -241,12 +229,63 @@ pub(crate) fn check(inst: &Inst, function: &Function, vcode: &VCode<Inst>) -> Pc
     }
 }
 
-fn check_addr(
+/// The operation we're checking against an amode: either
+///
+/// - a *load*, and we need to validate that the field's fact subsumes
+///   the load result's fact, OR
+///
+/// - a *store*, and we need to validate that the stored data's fact
+///   subsumes the field's fact.
+enum LoadOrStore<'a> {
+    Load { result_fact: Option<&'a Fact> },
+    Store { stored_fact: Option<&'a Fact> },
+}
+
+fn check_load(
+    ctx: &FactContext,
+    rd: Option<Reg>,
+    flags: MemFlags,
+    addr: &AMode,
+    vcode: &VCode<Inst>,
+    ty: Type,
+) -> PccResult<()> {
+    let result_fact = rd.map(|rd| get_fact(vcode, rd)).transpose()?;
+    check_addr(
+        ctx,
+        flags,
+        addr,
+        vcode,
+        ty,
+        LoadOrStore::Load { result_fact },
+    )
+}
+
+fn check_store(
     ctx: &FactContext,
+    rd: Option<Reg>,
     flags: MemFlags,
     addr: &AMode,
     vcode: &VCode<Inst>,
-    size: u8,
+    ty: Type,
+) -> PccResult<()> {
+    let stored_fact = rd.map(|rd| get_fact(vcode, rd)).transpose()?;
+    check_addr(
+        ctx,
+        flags,
+        addr,
+        vcode,
+        ty,
+        LoadOrStore::Store { stored_fact },
+    )
+}
+
+fn check_addr<'a>(
+    ctx: &FactContext,
+    flags: MemFlags,
+    addr: &AMode,
+    vcode: &VCode<Inst>,
+    ty: Type,
+    op: LoadOrStore<'a>,
 ) -> PccResult<()> {
     if !flags.checked() {
         return Ok(());
@@ -254,19 +293,36 @@ fn check_addr(
 
     trace!("check_addr: {:?}", addr);
 
+    let check = |addr: &Fact, ty: Type| -> PccResult<()> {
+        match op {
+            LoadOrStore::Load { result_fact } => {
+                let loaded_fact = ctx.load(addr, ty)?;
+                trace!(
+                    "checking a load: loaded_fact = {loaded_fact:?} result_fact = {result_fact:?}"
+                );
+                if ctx.subsumes_fact_optionals(loaded_fact, result_fact) {
+                    Ok(())
+                } else {
+                    Err(PccError::UnsupportedFact)
+                }
+            }
+            LoadOrStore::Store { stored_fact } => ctx.store(addr, ty, stored_fact),
+        }
+    };
+
     match addr {
         &AMode::RegReg { rn, rm } => {
             let rn = get_fact(vcode, rn)?;
             let rm = get_fact(vcode, rm)?;
             let sum = fail_if_missing(ctx.add(&rn, &rm, 64))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::RegScaled { rn, rm, ty } => {
             let rn = get_fact(vcode, rn)?;
             let rm = get_fact(vcode, rm)?;
             let rm_scaled = fail_if_missing(ctx.scale(&rm, 64, ty.bytes()))?;
             let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::RegScaledExtended {
             rn,
@@ -279,19 +335,22 @@ fn check_addr(
             let rm_extended = fail_if_missing(extend_fact(ctx, rm, extendop))?;
             let rm_scaled = fail_if_missing(ctx.scale(&rm_extended, 64, ty.bytes()))?;
             let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::RegExtended { rn, rm, extendop } => {
             let rn = get_fact(vcode, rn)?;
             let rm = get_fact(vcode, rm)?;
             let rm_extended = fail_if_missing(extend_fact(ctx, rm, extendop))?;
             let sum = fail_if_missing(ctx.add(&rn, &rm_extended, 64))?;
-            ctx.check_address(&sum, size.into())
+            trace!("rn = {rn:?} rm = {rm:?} rm_extended = {rm_extended:?} sum = {sum:?}");
+            check(&sum, ty)?;
+            trace!(" -> checks out!");
+            Ok(())
         }
         &AMode::Unscaled { rn, simm9 } => {
             let rn = get_fact(vcode, rn)?;
             let sum = fail_if_missing(ctx.offset(&rn, 64, simm9.value.into()))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::UnsignedOffset { rn, uimm12 } => {
             let rn = get_fact(vcode, rn)?;
@@ -299,12 +358,12 @@ fn check_addr(
             // most 32 or 64 for large vector ops, and the `uimm12`'s
             // value is at most 4095.
             let uimm12: u64 = uimm12.value.into();
-            let offset: u64 = uimm12.checked_mul(size.into()).unwrap();
+            let offset: u64 = uimm12.checked_mul(ty.bytes().into()).unwrap();
             // This `unwrap()` will always succeed because the value
             // will always be positive and much smaller than
             // `i64::MAX` (see above).
             let sum = fail_if_missing(ctx.offset(&rn, 64, i64::try_from(offset).unwrap()))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::Label { .. } | &AMode::Const { .. } => {
             // Always accept: labels and constants must be within the
@@ -314,7 +373,7 @@ fn check_addr(
         &AMode::RegOffset { rn, off, .. } => {
             let rn = get_fact(vcode, rn)?;
             let sum = fail_if_missing(ctx.offset(&rn, 64, off))?;
-            ctx.check_address(&sum, size.into())
+            check(&sum, ty)
         }
         &AMode::SPOffset { .. }
         | &AMode::FPOffset { .. }
@@ -328,7 +387,7 @@ fn check_addr(
     }
 }
 
-fn check_addr_pair(
+fn check_load_pair(
     _ctx: &FactContext,
     _flags: MemFlags,
     _addr: &PairAMode,
@@ -338,16 +397,42 @@ fn check_addr_pair(
     Err(PccError::UnimplementedInst)
 }
 
-fn check_scalar_addr(
+fn check_store_pair(
+    _ctx: &FactContext,
+    _flags: MemFlags,
+    _addr: &PairAMode,
+    _vcode: &VCode<Inst>,
+    _size: u8,
+) -> PccResult<()> {
+    Err(PccError::UnimplementedInst)
+}
+
+fn check_load_addr(
+    ctx: &FactContext,
+    flags: MemFlags,
+    reg: Reg,
+    vcode: &VCode<Inst>,
+    ty: Type,
+) -> PccResult<()> {
+    if !flags.checked() {
+        return Ok(());
+    }
+    let fact = get_fact(vcode, reg)?;
+    let _output_fact = ctx.load(fact, ty)?;
+    Ok(())
+}
+
+fn check_store_addr(
     ctx: &FactContext,
     flags: MemFlags,
     reg: Reg,
     vcode: &VCode<Inst>,
-    size: u8,
+    ty: Type,
 ) -> PccResult<()> {
     if !flags.checked() {
         return Ok(());
     }
     let fact = get_fact(vcode, reg)?;
-    ctx.check_address(fact, size.into())
+    let _output_fact = ctx.store(fact, ty, None)?;
+    Ok(())
 }
diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs
index 4b360fd96..9268a5c5a 100644
--- a/cranelift/codegen/src/machinst/lower.rs
+++ b/cranelift/codegen/src/machinst/lower.rs
@@ -8,7 +8,7 @@
 use crate::entity::SecondaryMap;
 use crate::fx::{FxHashMap, FxHashSet};
 use crate::inst_predicates::{has_lowering_side_effect, is_constant_64bit};
-use crate::ir::pcc::{PccError, PccResult};
+use crate::ir::pcc::{FactContext, PccError, PccResult};
 use crate::ir::{
     ArgumentPurpose, Block, Constant, ConstantData, DataFlowGraph, ExternalName, Function,
     GlobalValue, GlobalValueData, Immediate, Inst, InstructionData, MemFlags, RelSourceLoc, Type,
@@ -151,9 +151,9 @@ pub trait LowerBackend {
     /// on VRegs.
     fn check_fact(
         &self,
-        _inst: &Self::MInst,
-        _function: &Function,
+        _ctx: &FactContext<'_>,
         _vcode: &VCode<Self::MInst>,
+        _inst: &Self::MInst,
     ) -> PccResult<()> {
         Err(PccError::UnimplementedBackend)
     }
diff --git a/cranelift/codegen/src/machinst/vcode.rs b/cranelift/codegen/src/machinst/vcode.rs
index aa88e19c6..010452519 100644
--- a/cranelift/codegen/src/machinst/vcode.rs
+++ b/cranelift/codegen/src/machinst/vcode.rs
@@ -1290,7 +1290,13 @@ impl<I: VCodeInst> VCode<I> {
     /// Get the fact, if any, for a given VReg.
     pub fn vreg_fact(&self, vreg: VReg) -> Option<&Fact> {
         let vreg = self.resolve_vreg_alias(vreg);
-        self.facts[vreg.vreg()].as_ref()
+        match &self.facts[vreg.vreg()] {
+            // The vreg has a stated fact -- return that.
+            Some(fact) => Some(fact),
+            // The vreg has no fact, but maybe we can infer a minimal
+            // one (e.g., an integer range) from the type.
+            None => Fact::infer_from_type(self.vreg_types[vreg.vreg()]),
+        }
     }
 
     /// Does a given instruction define any facts?
@@ -1299,7 +1305,7 @@ impl<I: VCodeInst> VCode<I> {
             .iter()
             .filter(|o| o.kind() == OperandKind::Def)
             .map(|o| o.vreg())
-            .any(|vreg| self.vreg_fact(vreg).is_some())
+            .any(|vreg| self.facts[vreg.vreg()].is_some())
     }
 }
 
diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt
index 37ec57611..32f03d7ca 100644
--- a/crates/c-api/CMakeLists.txt
+++ b/crates/c-api/CMakeLists.txt
@@ -1,5 +1,6 @@
 cmake_minimum_required(VERSION 3.10)
 
+set(WASMTIME_USER_CARGO_BUILD_OPTIONS "" CACHE STRING "Additional cargo flags (such as --features) to apply to the build command")
 option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
 
 if(CMAKE_BUILD_TYPE STREQUAL "Release")
@@ -97,7 +98,7 @@ ExternalProject_Add(
 	DOWNLOAD_COMMAND ""
 	CONFIGURE_COMMAND ""
 	INSTALL_COMMAND "${WASMTIME_INSTALL_COMMAND}"
-        BUILD_COMMAND ${WASMTIME_PREBUILD_COMMAND} ${WASMTIME_CARGO_BINARY} build ${WASMTIME_BUILD_TYPE_FLAG} ${WASMTIME_BUILD_TARGET}
+        BUILD_COMMAND ${WASMTIME_PREBUILD_COMMAND} ${WASMTIME_CARGO_BINARY} build ${WASMTIME_BUILD_TYPE_FLAG} ${WASMTIME_USER_CARGO_BUILD_OPTIONS} ${WASMTIME_BUILD_TARGET}
 	BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}
 	BUILD_ALWAYS ON
 	BUILD_BYPRODUCTS ${WASMTIME_BUILD_PRODUCT})
diff --git a/crates/c-api/include/wasmtime/async.h b/crates/c-api/include/wasmtime/async.h
index 3919dc3f1..955944c72 100644
--- a/crates/c-api/include/wasmtime/async.h
+++ b/crates/c-api/include/wasmtime/async.h
@@ -353,7 +353,7 @@ typedef struct {
  */
 WASM_API_EXTERN void wasmtime_config_host_stack_creator_set(
     wasm_config_t*,
-    wasmtime_memory_creator_t*);
+    wasmtime_stack_creator_t*);
 
 
 #ifdef __cplusplus
diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h
index 38f1edc1b..38429f84b 100644
--- a/crates/c-api/include/wasmtime/config.h
+++ b/crates/c-api/include/wasmtime/config.h
@@ -460,6 +460,22 @@ WASM_API_EXTERN void wasmtime_config_host_memory_creator_set(
     wasm_config_t*,
     wasmtime_memory_creator_t*);
 
+/**
+ * \brief Configures whether copy-on-write memory-mapped data is used to initialize a linear memory.
+ *
+ * Initializing linear memory via a copy-on-write mapping can drastically improve instantiation costs 
+ * of a WebAssembly module because copying memory is deferred. Additionally if a page of memory is 
+ * only ever read from WebAssembly and never written too then the same underlying page of data will
+ * be reused between all instantiations of a module meaning that if a module is instantiated many
+ * times this can lower the overall memory required needed to run that module.
+ *
+ * This option defaults to true.
+ *
+ * For more information see the Rust documentation at
+ * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.memory_init_cow
+ */
+WASMTIME_CONFIG_PROP(void, memory_init_cow, bool)
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs
index bcf8acde6..201e05196 100644
--- a/crates/c-api/src/config.rs
+++ b/crates/c-api/src/config.rs
@@ -424,3 +424,8 @@ pub unsafe extern "C" fn wasmtime_config_host_memory_creator_set(
         new_memory: creator.new_memory,
     }));
 }
+
+#[no_mangle]
+pub extern "C" fn wasmtime_config_memory_init_cow_set(c: &mut wasm_config_t, enable: bool) {
+    c.config.memory_init_cow(enable);
+}
diff --git a/crates/wasi-http/wit/command-extended.wit b/crates/wasi-http/wit/command-extended.wit
index b4d08aed3..37e4fcb92 100644
--- a/crates/wasi-http/wit/command-extended.wit
+++ b/crates/wasi-http/wit/command-extended.wit
@@ -1,37 +1,6 @@
 // All of the same imports and exports available in the wasi:cli/command world
 // with addition of HTTP proxy related imports:
 world command-extended {
-  import wasi:clocks/wall-clock;
-  import wasi:clocks/monotonic-clock;
-  import wasi:clocks/timezone;
-  import wasi:filesystem/types;
-  import wasi:filesystem/preopens;
-  import wasi:sockets/instance-network;
-  import wasi:sockets/ip-name-lookup;
-  import wasi:sockets/network;
-  import wasi:sockets/tcp-create-socket;
-  import wasi:sockets/tcp;
-  import wasi:sockets/udp-create-socket;
-  import wasi:sockets/udp;
-  import wasi:random/random;
-  import wasi:random/insecure;
-  import wasi:random/insecure-seed;
-  import wasi:io/poll;
-  import wasi:io/streams;
-  import wasi:cli/environment;
-  import wasi:cli/exit;
-  import wasi:cli/stdin;
-  import wasi:cli/stdout;
-  import wasi:cli/stderr;
-  import wasi:cli/terminal-input;
-  import wasi:cli/terminal-output;
-  import wasi:cli/terminal-stdin;
-  import wasi:cli/terminal-stdout;
-  import wasi:cli/terminal-stderr;
-
-  // We should replace all others with `include self.command`
-  // as soon as the unioning of worlds is available:
-  // https://github.com/WebAssembly/component-model/issues/169
-  import wasi:logging/logging;
+  include wasi:cli/command;
   import wasi:http/outgoing-handler;
 }
diff --git a/crates/wasi-http/wit/deps/logging/logging.wit b/crates/wasi-http/wit/deps/logging/logging.wit
deleted file mode 100644
index da537e479..000000000
--- a/crates/wasi-http/wit/deps/logging/logging.wit
+++ /dev/null
@@ -1,37 +0,0 @@
-package wasi:logging;
-
-/// WASI Logging is a logging API intended to let users emit log messages with
-/// simple priority levels and context values.
-interface logging {
-    /// A log level, describing a kind of message.
-    enum level {
-       /// Describes messages about the values of variables and the flow of
-       /// control within a program.
-       trace,
-
-       /// Describes messages likely to be of interest to someone debugging a
-       /// program.
-       debug,
-
-       /// Describes messages likely to be of interest to someone monitoring a
-       /// program.
-       info,
-
-       /// Describes messages indicating hazardous situations.
-       warn,
-
-       /// Describes messages indicating serious errors.
-       error,
-
-       /// Describes messages indicating fatal errors.
-       critical,
-    }
-
-    /// Emit a log message.
-    ///
-    /// A log message has a `level` describing what kind of message is being
-    /// sent, a context, which is an uninterpreted string meant to help
-    /// consumers group similar messages, and a string containing the message
-    /// text.
-    log: func(level: level, context: string, message: string);
-}
diff --git a/crates/wasi-http/wit/deps/logging/world.wit b/crates/wasi-http/wit/deps/logging/world.wit
deleted file mode 100644
index ede628643..000000000
--- a/crates/wasi-http/wit/deps/logging/world.wit
+++ /dev/null
@@ -1,5 +0,0 @@
-package wasi:logging;
-
-world imports {
-    import logging;
-}
diff --git a/crates/wasi/wit/command-extended.wit b/crates/wasi/wit/command-extended.wit
index b4d08aed3..37e4fcb92 100644
--- a/crates/wasi/wit/command-extended.wit
+++ b/crates/wasi/wit/command-extended.wit
@@ -1,37 +1,6 @@
 // All of the same imports and exports available in the wasi:cli/command world
 // with addition of HTTP proxy related imports:
 world command-extended {
-  import wasi:clocks/wall-clock;
-  import wasi:clocks/monotonic-clock;
-  import wasi:clocks/timezone;
-  import wasi:filesystem/types;
-  import wasi:filesystem/preopens;
-  import wasi:sockets/instance-network;
-  import wasi:sockets/ip-name-lookup;
-  import wasi:sockets/network;
-  import wasi:sockets/tcp-create-socket;
-  import wasi:sockets/tcp;
-  import wasi:sockets/udp-create-socket;
-  import wasi:sockets/udp;
-  import wasi:random/random;
-  import wasi:random/insecure;
-  import wasi:random/insecure-seed;
-  import wasi:io/poll;
-  import wasi:io/streams;
-  import wasi:cli/environment;
-  import wasi:cli/exit;
-  import wasi:cli/stdin;
-  import wasi:cli/stdout;
-  import wasi:cli/stderr;
-  import wasi:cli/terminal-input;
-  import wasi:cli/terminal-output;
-  import wasi:cli/terminal-stdin;
-  import wasi:cli/terminal-stdout;
-  import wasi:cli/terminal-stderr;
-
-  // We should replace all others with `include self.command`
-  // as soon as the unioning of worlds is available:
-  // https://github.com/WebAssembly/component-model/issues/169
-  import wasi:logging/logging;
+  include wasi:cli/command;
   import wasi:http/outgoing-handler;
 }
diff --git a/crates/wasi/wit/deps/logging/logging.wit b/crates/wasi/wit/deps/logging/logging.wit
deleted file mode 100644
index da537e479..000000000
--- a/crates/wasi/wit/deps/logging/logging.wit
+++ /dev/null
@@ -1,37 +0,0 @@
-package wasi:logging;
-
-/// WASI Logging is a logging API intended to let users emit log messages with
-/// simple priority levels and context values.
-interface logging {
-    /// A log level, describing a kind of message.
-    enum level {
-       /// Describes messages about the values of variables and the flow of
-       /// control within a program.
-       trace,
-
-       /// Describes messages likely to be of interest to someone debugging a
-       /// program.
-       debug,
-
-       /// Describes messages likely to be of interest to someone monitoring a
-       /// program.
-       info,
-
-       /// Describes messages indicating hazardous situations.
-       warn,
-
-       /// Describes messages indicating serious errors.
-       error,
-
-       /// Describes messages indicating fatal errors.
-       critical,
-    }
-
-    /// Emit a log message.
-    ///
-    /// A log message has a `level` describing what kind of message is being
-    /// sent, a context, which is an uninterpreted string meant to help
-    /// consumers group similar messages, and a string containing the message
-    /// text.
-    log: func(level: level, context: string, message: string);
-}
diff --git a/crates/wasi/wit/deps/logging/world.wit b/crates/wasi/wit/deps/logging/world.wit
deleted file mode 100644
index ede628643..000000000
--- a/crates/wasi/wit/deps/logging/world.wit
+++ /dev/null
@@ -1,5 +0,0 @@
-package wasi:logging;
-
-world imports {
-    import logging;
-}
diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs
index d4bdd3bdd..61d31f6a8 100644
--- a/fuzz/fuzz_targets/differential.rs
+++ b/fuzz/fuzz_targets/differential.rs
@@ -375,6 +375,8 @@ fn winch_supports_module(module: &[u8]) -> bool {
                         | F64Abs { .. }
                         | F32Neg { .. }
                         | F64Neg { .. }
+                        | F32Sqrt { .. }
+                        | F64Sqrt { .. }
                         | CallIndirect { .. }
                         | ElemDrop { .. }
                         | TableCopy { .. }
diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs
index f6032a946..e6fb506c1 100644
--- a/winch/codegen/src/isa/aarch64/masm.rs
+++ b/winch/codegen/src/isa/aarch64/masm.rs
@@ -262,6 +262,10 @@ impl Masm for MacroAssembler {
         todo!();
     }
 
+    fn float_sqrt(&mut self, _dst: Reg, _src: Reg, _size: OperandSize) {
+        todo!()
+    }
+
     fn and(&mut self, _dst: Reg, _lhs: Reg, _rhs: RegImm, _size: OperandSize) {
         todo!()
     }
diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs
index 6b94c5ad7..afd1a2a71 100644
--- a/winch/codegen/src/isa/x64/asm.rs
+++ b/winch/codegen/src/isa/x64/asm.rs
@@ -845,6 +845,20 @@ impl Assembler {
         })
     }
 
+    pub fn sqrt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+        let op = match size {
+            OperandSize::S32 => SseOpcode::Sqrtss,
+            OperandSize::S64 => SseOpcode::Sqrtsd,
+            OperandSize::S128 => unreachable!(),
+        };
+
+        self.emit(Inst::XmmUnaryRmR {
+            op,
+            src: Xmm::from(src).into(),
+            dst: dst.into(),
+        })
+    }
+
     /// Emit a call to an unknown location through a register.
     pub fn call_with_reg(&mut self, callee: Reg) {
         self.emit(Inst::CallUnknown {
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 25264de83..182d8f902 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -404,6 +404,10 @@ impl Masm for MacroAssembler {
         }
     }
 
+    fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize) {
+        self.asm.sqrt(src, dst, size);
+    }
+
     fn and(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         Self::ensure_two_argument_form(&dst, &lhs);
         match (rhs, dst) {
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index ab032cfb7..3c597e709 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -383,6 +383,9 @@ pub(crate) trait MacroAssembler {
     /// Perform a floating point floor operation.
     fn float_round(&mut self, mode: RoundingMode, dst: Reg, src: RegImm, size: OperandSize);
 
+    /// Perform a floating point square root operation.
+    fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize);
+
     /// Perform logical and operation.
     fn and(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 40a290666..53143f4b4 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -56,6 +56,8 @@ macro_rules! def_unsupported {
     (emit F64Nearest $($rest:tt)*) => {};
     (emit F32Trunc $($rest:tt)*) => {};
     (emit F64Trunc $($rest:tt)*) => {};
+    (emit F32Sqrt $($rest:tt)*) => {};
+    (emit F64Sqrt $($rest:tt)*) => {};
     (emit I32Add $($rest:tt)*) => {};
     (emit I64Add $($rest:tt)*) => {};
     (emit I32Sub $($rest:tt)*) => {};
@@ -252,6 +254,20 @@ where
             });
     }
 
+    fn visit_f32_sqrt(&mut self) {
+        self.context
+            .unop(self.masm, OperandSize::S32, &mut |masm, reg, size| {
+                masm.float_sqrt(reg, reg, size);
+            });
+    }
+
+    fn visit_f64_sqrt(&mut self) {
+        self.context
+            .unop(self.masm, OperandSize::S64, &mut |masm, reg, size| {
+                masm.float_sqrt(reg, reg, size);
+            });
+    }
+
     fn visit_i32_add(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
             masm.add(dst, dst, src, size);


Test patch:
diff --git a/cranelift/filetests/filetests/pcc/fail/blockparams.clif b/cranelift/filetests/filetests/pcc/fail/blockparams.clif
new file mode 100644
index 000000000..29863e06b
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/fail/blockparams.clif
@@ -0,0 +1,19 @@
+test compile expect-fail
+set enable_pcc=true
+target aarch64
+
+function %f0(i64, i32) -> i64 {
+block0(v0 ! max(64, 0x100): i64, v1: i32):
+    v2 ! max(64, 0x100) = iconst.i64 0x100
+    v3 ! max(64, 0x200) = iadd v0, v2
+    brif v1, block1(v0), block2(v3)
+
+block1(v4 ! max(64, 0xff): i64):  ;; shrink the range -- should be caught
+    jump block3(v4)
+
+block2(v5 ! max(64, 0x1ff): i64):
+    jump block3(v5)
+
+block3(v6 ! max(64, 1): i64):
+    return v6
+}
diff --git a/cranelift/filetests/filetests/pcc/fail/struct.clif b/cranelift/filetests/filetests/pcc/fail/struct.clif
new file mode 100644
index 000000000..22e455bea
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/fail/struct.clif
@@ -0,0 +1,19 @@
+test compile expect-fail
+set enable_pcc=true
+target aarch64
+
+function %f0(i64) -> i64 {
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt1 = memory 0x1_0000_0000
+block0(v0 ! mem(mt0, 0): i64):
+    v1 ! mem(mt1, 8) = load.i64 checked v0
+    return v1
+}
+
+function %f1(i64, i64) {
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt1 = memory 0x1_0000_0000
+block0(v0 ! mem(mt0, 0): i64, v1 ! mem(mt1, 8): i64):
+    store.i64 checked v1, v0
+    return
+}
diff --git a/cranelift/filetests/filetests/pcc/fail/vmctx.clif b/cranelift/filetests/filetests/pcc/fail/vmctx.clif
new file mode 100644
index 000000000..f29e17344
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/fail/vmctx.clif
@@ -0,0 +1,37 @@
+test compile expect-fail
+set enable_pcc=true
+target aarch64
+
+;; Equivalent to a Wasm `i64.load` from a static memory.
+function %f0(i64, i32) -> i64 {
+    ;; mock vmctx struct:
+    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0) }
+    ;; mock static memory: 4GiB range, *but insufficient guard*
+    mt1 = memory 0x1_0000_0000
+
+block0(v0 ! mem(mt0, 0): i64, v1: i32):
+    ;; Compute the address: base + offset. Guard region (2GiB) is
+    ;; sufficient for an 8-byte I64 load.
+    v2 ! mem(mt1, 0) = load.i64 checked v0+0    ;; base pointer
+    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v5 = load.i64 checked v4
+    return v5
+}
+
+;; Equivalent to a Wasm `i64.load` from a static memory.
+function %f1(i64, i32) -> i64 {
+    ;; mock vmctx struct:
+    mt0 = struct 16 { 0: i64 readonly ! mem(mt1, 0), 8: i64 readonly }
+    ;; mock static memory: 4GiB range, *but insufficient guard*
+    mt1 = memory 0x1_8000_0000
+
+block0(v0 ! mem(mt0, 0): i64, v1: i32):
+    ;; Compute the address: base + offset. Guard region (2GiB) is
+    ;; sufficient for an 8-byte I64 load.
+    v2 ! mem(mt1, 0) = load.i64 checked v0+8    ;; base pointer, but the wrong one
+    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v5 = load.i64 checked v4
+    return v5
+}
diff --git a/cranelift/filetests/filetests/pcc/succeed/blockparams.clif b/cranelift/filetests/filetests/pcc/succeed/blockparams.clif
new file mode 100644
index 000000000..092c1838c
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/blockparams.clif
@@ -0,0 +1,19 @@
+test compile
+set enable_pcc=true
+target aarch64
+
+function %f0(i64, i32) -> i64 {
+block0(v0 ! max(64, 0x100): i64, v1: i32):
+    v2 ! max(64, 0x100) = iconst.i64 0x100
+    v3 ! max(64, 0x200) = iadd v0, v2
+    brif v1, block1(v0), block2(v3)
+
+block1(v4 ! max(64, 0x1000): i64):  ;; broaden the range -- always allowed
+    jump block3(v4)
+
+block2(v5 ! max(64, 0x2000): i64):
+    jump block3(v5)
+
+block3(v6 ! max(64, 0x2001): i64):
+    return v6
+}
diff --git a/cranelift/filetests/filetests/pcc/succeed/struct.clif b/cranelift/filetests/filetests/pcc/succeed/struct.clif
new file mode 100644
index 000000000..6555b22ad
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/struct.clif
@@ -0,0 +1,19 @@
+test compile
+set enable_pcc=true
+target aarch64
+
+function %f0(i64) -> i64 {
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt1 = memory 0x1_0000_0000
+block0(v0 ! mem(mt0, 0): i64):
+    v1 ! mem(mt1, 0) = load.i64 checked v0
+    return v1
+}
+
+function %f1(i64, i64) {
+    mt0 = struct 8 { 0: i64 ! mem(mt1, 0) }
+    mt1 = memory 0x1_0000_0000
+block0(v0 ! mem(mt0, 0): i64, v1 ! mem(mt1, 0): i64):
+    store.i64 checked v1, v0
+    return
+}
diff --git a/cranelift/filetests/filetests/pcc/succeed/vmctx.clif b/cranelift/filetests/filetests/pcc/succeed/vmctx.clif
new file mode 100644
index 000000000..974718293
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/vmctx.clif
@@ -0,0 +1,20 @@
+test compile
+set enable_pcc=true
+target aarch64
+
+;; Equivalent to a Wasm `i64.load` from a static memory.
+function %f0(i64, i32) -> i64 {
+    ;; mock vmctx struct:
+    mt0 = struct 8 { 0: i64 readonly ! mem(mt1, 0) }
+    ;; mock static memory: 4GiB range, plus 2GiB guard
+    mt1 = memory 0x1_8000_0000
+
+block0(v0 ! mem(mt0, 0): i64, v1: i32):
+    ;; Compute the address: base + offset. Guard region (2GiB) is
+    ;; sufficient for an 8-byte I64 load.
+    v2 ! mem(mt1, 0) = load.i64 checked v0+0    ;; base pointer
+    v3 ! max(64, 0xffff_ffff) = uextend.i64 v1  ;; offset
+    v4 ! mem(mt1, 0xffff_ffff) = iadd.i64 v2, v3
+    v5 = load.i64 checked v4
+    return v5
+}
diff --git a/crates/test-programs/src/bin/cli_panic.rs b/crates/test-programs/src/bin/cli_panic.rs
deleted file mode 100644
index bf9d3b49e..000000000
--- a/crates/test-programs/src/bin/cli_panic.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
-    panic!("idk!!!");
-}
diff --git a/crates/test-programs/src/bin/cli_stream_pollable_lifetimes.rs b/crates/test-programs/src/bin/cli_stream_pollable_lifetimes.rs
deleted file mode 100644
index 11fa0abc4..000000000
--- a/crates/test-programs/src/bin/cli_stream_pollable_lifetimes.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use test_programs::wasi::cli::environment;
-use test_programs::wasi::cli::stdin;
-use test_programs::wasi::io::poll;
-use test_programs::wasi::io::streams;
-
-fn main() {
-    let args = environment::get_arguments();
-    let args = &args[1..];
-
-    if args == &["correct"] {
-        let stdin: streams::InputStream = stdin::get_stdin();
-        let stdin_pollable = stdin.subscribe();
-        let ready = poll::poll_list(&[&stdin_pollable]);
-        assert_eq!(ready, &[0]);
-        drop(stdin_pollable);
-        drop(stdin);
-    } else if args == &["trap"] {
-        let stdin: streams::InputStream = stdin::get_stdin();
-        let stdin_pollable = stdin.subscribe();
-        let ready = poll::poll_list(&[&stdin_pollable]);
-        assert_eq!(ready, &[0]);
-        drop(stdin);
-        unreachable!(
-            "execution should have trapped in line above when stream dropped before pollable"
-        );
-    } else {
-        panic!("bad value for args: expected `[\"correct\"]` or `[\"trap\"]`, got {args:?}")
-    }
-}
diff --git a/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs b/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs
new file mode 100644
index 000000000..dfb155734
--- /dev/null
+++ b/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs
@@ -0,0 +1,12 @@
+use test_programs::wasi::cli::stdin;
+use test_programs::wasi::io::poll;
+use test_programs::wasi::io::streams;
+
+fn main() {
+    let stdin: streams::InputStream = stdin::get_stdin();
+    let stdin_pollable = stdin.subscribe();
+    let ready = poll::poll_list(&[&stdin_pollable]);
+    assert_eq!(ready, &[0]);
+    drop(stdin_pollable);
+    drop(stdin);
+}
diff --git a/crates/test-programs/src/bin/preview2_stream_pollable_traps.rs b/crates/test-programs/src/bin/preview2_stream_pollable_traps.rs
new file mode 100644
index 000000000..13331fb02
--- /dev/null
+++ b/crates/test-programs/src/bin/preview2_stream_pollable_traps.rs
@@ -0,0 +1,12 @@
+use test_programs::wasi::cli::stdin;
+use test_programs::wasi::io::poll;
+use test_programs::wasi::io::streams;
+
+fn main() {
+    let stdin: streams::InputStream = stdin::get_stdin();
+    let stdin_pollable = stdin.subscribe();
+    let ready = poll::poll_list(&[&stdin_pollable]);
+    assert_eq!(ready, &[0]);
+    drop(stdin);
+    unreachable!("execution should have trapped in line above when stream dropped before pollable");
+}
diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs
index c6e60d1e6..c1813476b 100644
--- a/crates/wasi/tests/all/async_.rs
+++ b/crates/wasi/tests/all/async_.rs
@@ -328,3 +328,19 @@ async fn preview2_udp_sample_application() {
 async fn preview2_tcp_connect() {
     run(PREVIEW2_TCP_CONNECT_COMPONENT, false).await.unwrap()
 }
+#[test_log::test(tokio::test(flavor = "multi_thread"))]
+async fn preview2_stream_pollable_correct() {
+    run(PREVIEW2_STREAM_POLLABLE_CORRECT_COMPONENT, false)
+        .await
+        .unwrap()
+}
+#[test_log::test(tokio::test(flavor = "multi_thread"))]
+async fn preview2_stream_pollable_traps() {
+    let e = run(PREVIEW2_STREAM_POLLABLE_TRAPS_COMPONENT, false)
+        .await
+        .unwrap_err();
+    assert_eq!(
+        format!("{}", e.source().expect("trap source")),
+        "entry still has children"
+    )
+}
diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs
index c7e9da1d7..4347e01d9 100644
--- a/crates/wasi/tests/all/sync.rs
+++ b/crates/wasi/tests/all/sync.rs
@@ -269,3 +269,15 @@ fn preview2_udp_sample_application() {
 fn preview2_tcp_connect() {
     run(PREVIEW2_TCP_CONNECT_COMPONENT, false).unwrap()
 }
+#[test_log::test]
+fn preview2_stream_pollable_correct() {
+    run(PREVIEW2_STREAM_POLLABLE_CORRECT_COMPONENT, false).unwrap()
+}
+#[test_log::test]
+fn preview2_stream_pollable_traps() {
+    let e = run(PREVIEW2_STREAM_POLLABLE_TRAPS_COMPONENT, false).unwrap_err();
+    assert_eq!(
+        format!("{}", e.source().expect("trap source")),
+        "entry still has children"
+    )
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 93e19b64c..997738cdd 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1026,29 +1026,6 @@ mod test_programs {
         Ok(())
     }
 
-    #[test]
-    fn cli_panic() -> Result<()> {
-        let output = get_wasmtime_command()?
-            .args(&[
-                "run",
-                "-Wcomponent-model",
-                CLI_PANIC_COMPONENT,
-                "diesel",
-                "the",
-                "cat",
-                "scratched",
-                "me",
-                "real",
-                "good",
-                "yesterday",
-            ])
-            .output()?;
-        assert!(!output.status.success());
-        let stderr = String::from_utf8_lossy(&output.stderr);
-        assert!(stderr.contains("idk!!!"));
-        Ok(())
-    }
-
     #[test]
     fn cli_args() -> Result<()> {
         run_wasmtime(&[
@@ -1182,6 +1159,8 @@ mod test_programs {
             .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
             .output()?;
         assert!(!output.status.success());
+        let stderr = String::from_utf8_lossy(&output.stderr);
+        assert!(stderr.contains("Curiouser and curiouser!"));
         Ok(())
     }
 
@@ -1221,32 +1200,6 @@ mod test_programs {
         Ok(())
     }
 
-    #[test]
-    fn cli_stream_pollable_lifetimes() -> Result<()> {
-        // Test program has two modes, dispatching based on argument.
-        run_wasmtime(&[
-            "run",
-            "-Wcomponent-model",
-            CLI_STREAM_POLLABLE_LIFETIMES_COMPONENT,
-            "correct",
-        ])?;
-        let output = get_wasmtime_command()?
-            .args(&[
-                "run",
-                "-Wcomponent-model",
-                CLI_STREAM_POLLABLE_LIFETIMES_COMPONENT,
-                "trap",
-            ])
-            .output()?;
-        assert!(!output.status.success());
-        let stderr = String::from_utf8_lossy(&output.stderr);
-        assert!(
-            stderr.contains("entry still has children"),
-            "bad stderr: {stderr}"
-        );
-        Ok(())
-    }
-
     #[test]
     fn run_wasi_http_component() -> Result<()> {
         let output = super::run_wasmtime_for_output(
diff --git a/tests/misc_testsuite/winch/f32.wast b/tests/misc_testsuite/winch/f32.wast
index 5d8497461..96ab0d8d2 100644
--- a/tests/misc_testsuite/winch/f32.wast
+++ b/tests/misc_testsuite/winch/f32.wast
@@ -306,12 +306,33 @@
 
 
 (module
+  (func (export "sqrt") (param $x f32) (result f32) (f32.sqrt (local.get $x)))
   (func (export "ceil") (param $x f32) (result f32) (f32.ceil (local.get $x)))
   (func (export "floor") (param $x f32) (result f32) (f32.floor (local.get $x)))
   (func (export "trunc") (param $x f32) (result f32) (f32.trunc (local.get $x)))
   (func (export "nearest") (param $x f32) (result f32) (f32.nearest (local.get $x)))
 )
 
+(assert_return (invoke "sqrt" (f32.const -0x0p+0)) (f32.const -0x0p+0))
+(assert_return (invoke "sqrt" (f32.const 0x0p+0)) (f32.const 0x0p+0))
+(assert_return (invoke "sqrt" (f32.const -0x1p-149)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1p-149)) (f32.const 0x1.6a09e6p-75))
+(assert_return (invoke "sqrt" (f32.const -0x1p-126)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1p-126)) (f32.const 0x1p-63))
+(assert_return (invoke "sqrt" (f32.const -0x1p-1)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1p-1)) (f32.const 0x1.6a09e6p-1))
+(assert_return (invoke "sqrt" (f32.const -0x1p+0)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1p+0)) (f32.const 0x1p+0))
+(assert_return (invoke "sqrt" (f32.const -0x1.921fb6p+2)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1.921fb6p+2)) (f32.const 0x1.40d932p+1))
+(assert_return (invoke "sqrt" (f32.const -0x1.fffffep+127)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const 0x1.fffffep+127)) (f32.const 0x1.fffffep+63))
+(assert_return (invoke "sqrt" (f32.const -inf)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const inf)) (f32.const inf))
+(assert_return (invoke "sqrt" (f32.const -nan)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const -nan:0x200000)) (f32.const nan:arithmetic))
+(assert_return (invoke "sqrt" (f32.const nan)) (f32.const nan:canonical))
+(assert_return (invoke "sqrt" (f32.const nan:0x200000)) (f32.const nan:arithmetic))
 (assert_return (invoke "floor" (f32.const -0x0p+0)) (f32.const -0x0p+0))
 (assert_return (invoke "floor" (f32.const 0x0p+0)) (f32.const 0x0p+0))
 (assert_return (invoke "floor" (f32.const -0x1p-149)) (f32.const -0x1p+0))
diff --git a/tests/misc_testsuite/winch/f64.wast b/tests/misc_testsuite/winch/f64.wast
index 7acb99be6..9792f6d45 100644
--- a/tests/misc_testsuite/winch/f64.wast
+++ b/tests/misc_testsuite/winch/f64.wast
@@ -329,12 +329,33 @@
 (assert_return (invoke "type-first-f64") (f64.const 1.64))
 
 (module
+  (func (export "sqrt") (param $x f64) (result f64) (f64.sqrt (local.get $x)))
   (func (export "ceil") (param $x f64) (result f64) (f64.ceil (local.get $x)))
   (func (export "floor") (param $x f64) (result f64) (f64.floor (local.get $x)))
   (func (export "trunc") (param $x f64) (result f64) (f64.trunc (local.get $x)))
   (func (export "nearest") (param $x f64) (result f64) (f64.nearest (local.get $x)))
 )
 
+(assert_return (invoke "sqrt" (f64.const -0x0p+0)) (f64.const -0x0p+0))
+(assert_return (invoke "sqrt" (f64.const 0x0p+0)) (f64.const 0x0p+0))
+(assert_return (invoke "sqrt" (f64.const -0x0.0000000000001p-1022)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x0.0000000000001p-1022)) (f64.const 0x1p-537))
+(assert_return (invoke "sqrt" (f64.const -0x1p-1022)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x1p-1022)) (f64.const 0x1p-511))
+(assert_return (invoke "sqrt" (f64.const -0x1p-1)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x1p-1)) (f64.const 0x1.6a09e667f3bcdp-1))
+(assert_return (invoke "sqrt" (f64.const -0x1p+0)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x1p+0)) (f64.const 0x1p+0))
+(assert_return (invoke "sqrt" (f64.const -0x1.921fb54442d18p+2)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x1.921fb54442d18p+2)) (f64.const 0x1.40d931ff62705p+1))
+(assert_return (invoke "sqrt" (f64.const -0x1.fffffffffffffp+1023)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const 0x1.fffffffffffffp+1023)) (f64.const 0x1.fffffffffffffp+511))
+(assert_return (invoke "sqrt" (f64.const -inf)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const inf)) (f64.const inf))
+(assert_return (invoke "sqrt" (f64.const -nan)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const -nan:0x4000000000000)) (f64.const nan:arithmetic))
+(assert_return (invoke "sqrt" (f64.const nan)) (f64.const nan:canonical))
+(assert_return (invoke "sqrt" (f64.const nan:0x4000000000000)) (f64.const nan:arithmetic))
 (assert_return (invoke "floor" (f64.const -0x0p+0)) (f64.const -0x0p+0))
 (assert_return (invoke "floor" (f64.const 0x0p+0)) (f64.const 0x0p+0))
 (assert_return (invoke "floor" (f64.const -0x0.0000000000001p-1022)) (f64.const -0x1p+0))
diff --git a/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_const.wat b/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_const.wat
new file mode 100644
index 000000000..0cc61434b
--- /dev/null
+++ b/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_const.wat
@@ -0,0 +1,21 @@
+;;! target = "x86_64"
+
+(module
+    (func (result f32)
+        (f32.const 1.32)
+        (f32.sqrt)
+    )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec08             	sub	rsp, 8
+;;    8:	 4c893424             	mov	qword ptr [rsp], r14
+;;    c:	 f30f10050c000000     	movss	xmm0, dword ptr [rip + 0xc]
+;;   14:	 f30f51c0             	sqrtss	xmm0, xmm0
+;;   18:	 4883c408             	add	rsp, 8
+;;   1c:	 5d                   	pop	rbp
+;;   1d:	 c3                   	ret	
+;;   1e:	 0000                 	add	byte ptr [rax], al
+;;   20:	 c3                   	ret	
+;;   21:	 f5                   	cmc	
+;;   22:	 a83f                 	test	al, 0x3f
diff --git a/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_param.wat b/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_param.wat
new file mode 100644
index 000000000..6e096d088
--- /dev/null
+++ b/winch/filetests/filetests/x64/f32_sqrt/f32_sqrt_param.wat
@@ -0,0 +1,18 @@
+;;! target = "x86_64"
+
+(module
+    (func (param f32) (result f32)
+        (local.get 0)
+        (f32.sqrt)
+    )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 f30f1144240c         	movss	dword ptr [rsp + 0xc], xmm0
+;;    e:	 4c89742404           	mov	qword ptr [rsp + 4], r14
+;;   13:	 f30f1044240c         	movss	xmm0, dword ptr [rsp + 0xc]
+;;   19:	 f30f51c0             	sqrtss	xmm0, xmm0
+;;   1d:	 4883c410             	add	rsp, 0x10
+;;   21:	 5d                   	pop	rbp
+;;   22:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_const.wat b/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_const.wat
new file mode 100644
index 000000000..0b70b0885
--- /dev/null
+++ b/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_const.wat
@@ -0,0 +1,18 @@
+;;! target = "x86_64"
+
+(module
+    (func (result f64)
+        (f64.const 1.32)
+        (f64.sqrt)
+    )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec08             	sub	rsp, 8
+;;    8:	 4c893424             	mov	qword ptr [rsp], r14
+;;    c:	 f20f10050c000000     	movsd	xmm0, qword ptr [rip + 0xc]
+;;   14:	 f20f51c0             	sqrtsd	xmm0, xmm0
+;;   18:	 4883c408             	add	rsp, 8
+;;   1c:	 5d                   	pop	rbp
+;;   1d:	 c3                   	ret	
+;;   1e:	 0000                 	add	byte ptr [rax], al
diff --git a/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_param.wat b/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_param.wat
new file mode 100644
index 000000000..9b06a9637
--- /dev/null
+++ b/winch/filetests/filetests/x64/f64_sqrt/f64_sqrt_param.wat
@@ -0,0 +1,18 @@
+;;! target = "x86_64"
+
+(module
+    (func (param f64) (result f64)
+        (local.get 0)
+        (f64.sqrt)
+    )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 f20f11442408         	movsd	qword ptr [rsp + 8], xmm0
+;;    e:	 4c893424             	mov	qword ptr [rsp], r14
+;;   12:	 f20f10442408         	movsd	xmm0, qword ptr [rsp + 8]
+;;   18:	 f20f51c0             	sqrtsd	xmm0, xmm0
+;;   1c:	 4883c410             	add	rsp, 0x10
+;;   20:	 5d                   	pop	rbp
+;;   21:	 c3                   	ret