Repository: bytecodealliance/wasmtime
Pull request #8139 (2024-03-14 20:45:37)

cranelift: Optimize select_spectre_guard, carefully
This commit makes two changes to our treatment of
`select_spectre_guard`.

First, stop annotating this instruction as having any side effects. We only care that if its value result is used, then it's computed without branching on the condition input. We don't otherwise care when the value is computed, or if it's computed at all.

Second, introduce some carefully selected ISLE egraph rewrites for this instruction. These particular rewrites are those where we can statically determine which SSA value will be the result of the instruction. Since there is no actual choice involved, there's no way to accidentally introduce a branch on the condition input.

jameysharp: Perhaps @cfallin should review this? I was talking about it with him yesterday.
cfallin: Sure, happy to take a look.
github-actions[bot]: #### Subscribe to Label Action

cc @cfallin, @fitzgen

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

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

* cfallin: isle
* fitzgen: isle

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

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
jameysharp: I think I've addressed all your comments about documentation, Chris, and I'd appreciate feedback on that aspect. I still need to write filetests exercising these new rules, but at least now we can see that this has a positive effect on the typed-funcref tests, as expected.

Patch:
diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml
index 0f4b3bcb8..4b7486336 100644
--- a/.github/workflows/cargo-audit.yml
+++ b/.github/workflows/cargo-audit.yml
@@ -10,6 +10,6 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions-rs/audit-check@v1
+      - uses: rustsec/audit-check@v1.4.1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e606cf6bb..f671b6176 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -340,7 +340,7 @@ jobs:
     # Check whether `wasmtime` cross-compiles to aarch64-linux-android
     - run: rustup target add aarch64-linux-android
     - name: Set up JDK 17
-      uses: actions/setup-java@v3
+      uses: actions/setup-java@v4
       with:
         java-version: '17'
         distribution: 'temurin'
diff --git a/RELEASES.md b/RELEASES.md
index 9a554aed0..52b80b5b5 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -16,7 +16,7 @@ Unreleased.
 
 ## 19.0.0
 
-Unreleased.
+Released 2024-03-20
 
 ### Added
 
diff --git a/build.rs b/build.rs
index 262b40838..d0b42afe2 100644
--- a/build.rs
+++ b/build.rs
@@ -241,6 +241,10 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
             return denylist || ref_types || simd;
         }
 
+        if testsuite == "memory64" {
+            return testname.starts_with("simd") || testname.starts_with("threads");
+        }
+
         if testsuite != "winch" {
             return true;
         }
diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs
index d5c5f7a57..70b62f580 100644
--- a/cranelift/codegen/build.rs
+++ b/cranelift/codegen/build.rs
@@ -231,8 +231,9 @@ fn get_isle_compilations(
                     src_opts.join("icmp.isle"),
                     src_opts.join("remat.isle"),
                     src_opts.join("selects.isle"),
-                    src_opts.join("spaceship.isle"),
                     src_opts.join("shifts.isle"),
+                    src_opts.join("spaceship.isle"),
+                    src_opts.join("spectre.isle"),
                     src_opts.join("vector.isle"),
                 ],
                 untracked_inputs: vec![clif_opt_isle],
diff --git a/cranelift/codegen/meta/src/shared/instructions.rs b/cranelift/codegen/meta/src/shared/instructions.rs
index 5e75ac3a4..909f80160 100644
--- a/cranelift/codegen/meta/src/shared/instructions.rs
+++ b/cranelift/codegen/meta/src/shared/instructions.rs
@@ -1449,19 +1449,36 @@ pub(crate) fn define(
             Conditional select intended for Spectre guards.
 
             This operation is semantically equivalent to a select instruction.
-            However, it is guaranteed to not be removed or otherwise altered by any
-            optimization pass, and is guaranteed to result in a conditional-move
-            instruction, not a branch-based lowering.  As such, it is suitable
-            for use when producing Spectre guards. For example, a bounds-check
-            may guard against unsafe speculation past a bounds-check conditional
-            branch by passing the address or index to be accessed through a
-            conditional move, also gated on the same condition. Because no
-            Spectre-vulnerable processors are known to perform speculation on
-            conditional move instructions, this is guaranteed to pick the
-            correct input. If the selected input in case of overflow is a "safe"
-            value, for example a null pointer that causes an exception in the
-            speculative path, this ensures that no Spectre vulnerability will
-            exist.
+            However, this instruction prohibits all speculation on the
+            controlling value when determining which input to use as the result.
+            As such, it is suitable for use in Spectre guards.
+
+            For example, on a target which may speculatively execute branches,
+            the lowering of this instruction is guaranteed to not conditionally
+            branch. Instead it will typically lower to a conditional move
+            instruction. (No Spectre-vulnerable processors are known to perform
+            value speculation on conditional move instructions.)
+
+            Ensure that the instruction you're trying to protect from Spectre
+            attacks has a data dependency on the result of this instruction.
+            That prevents an out-of-order CPU from evaluating that instruction
+            until the result of this one is known, which in turn will be blocked
+            until the controlling value is known.
+
+            Typical usage is to use a bounds-check as the controlling value,
+            and select between either a null pointer if the bounds-check
+            fails, or an in-bounds address otherwise, so that dereferencing
+            the resulting address with a load or store instruction will trap if
+            the bounds-check failed. When this instruction is used in this way,
+            any microarchitectural side effects of the memory access will only
+            occur after the bounds-check finishes, which ensures that no Spectre
+            vulnerability will exist.
+
+            Optimization opportunities for this instruction are limited compared
+            to a normal select instruction, but it is allowed to be replaced
+            by other values which are functionally equivalent as long as doing
+            so does not introduce any new opportunities to speculate on the
+            controlling value.
             "#,
             &formats.ternary,
         )
@@ -1470,11 +1487,7 @@ pub(crate) fn define(
             Operand::new("x", Any).with_doc("Value to use when `c` is true"),
             Operand::new("y", Any).with_doc("Value to use when `c` is false"),
         ])
-        .operands_out(vec![Operand::new("a", Any)])
-        .other_side_effects()
-        // We can de-duplicate spectre selects since the side effect is
-        // idempotent.
-        .side_effects_idempotent(),
+        .operands_out(vec![Operand::new("a", Any)]),
     );
 
     ig.push(
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index 440840c4d..31a5c25c3 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -99,17 +99,14 @@ impl ABIMachineSpec for AArch64MachineDeps {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         if matches!(call_conv, isa::CallConv::Tail | isa::CallConv::Winch) {
             return compute_arg_locs_tail(params, add_ret_area_ptr, args);
         }
@@ -1291,14 +1288,11 @@ impl AArch64CallSite {
     }
 }
 
-fn compute_arg_locs_tail<'a, I>(
-    params: I,
+fn compute_arg_locs_tail(
+    params: &[ir::AbiParam],
     add_ret_area_ptr: bool,
-    mut args: ArgsAccumulator<'_>,
-) -> CodegenResult<(u32, Option<usize>)>
-where
-    I: IntoIterator<Item = &'a ir::AbiParam>,
-{
+    mut args: ArgsAccumulator,
+) -> CodegenResult<(u32, Option<usize>)> {
     let mut xregs = ALL_CLOBBERS
         .into_iter()
         .filter(|r| r.class() == RegClass::Int)
diff --git a/cranelift/codegen/src/isa/riscv64/abi.rs b/cranelift/codegen/src/isa/riscv64/abi.rs
index 0f0d32e8f..c119fe312 100644
--- a/cranelift/codegen/src/isa/riscv64/abi.rs
+++ b/cranelift/codegen/src/isa/riscv64/abi.rs
@@ -87,17 +87,14 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         assert_ne!(
             call_conv,
             isa::CallConv::Winch,
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index af34bb5a4..7c48578a8 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -220,17 +220,14 @@ impl ABIMachineSpec for S390xMachineDeps {
         8
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         _flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         assert_ne!(
             call_conv,
             isa::CallConv::Tail,
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index eeda5b6cd..9b8eb0764 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -101,17 +101,14 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         16
     }
 
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        mut args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>,
-    {
+        mut args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)> {
         let is_fastcall = call_conv.extends_windows_fastcall();
 
         let mut next_gpr = 0;
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 8e16fdae8..4af7bc343 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -408,16 +408,14 @@ pub trait ABIMachineSpec {
     /// Returns the stack-space used (rounded up to as alignment requires), and
     /// if `add_ret_area_ptr` was passed, the index of the extra synthetic arg
     /// that was added.
-    fn compute_arg_locs<'a, I>(
+    fn compute_arg_locs(
         call_conv: isa::CallConv,
         flags: &settings::Flags,
-        params: I,
+        params: &[ir::AbiParam],
         args_or_rets: ArgsOrRets,
         add_ret_area_ptr: bool,
-        args: ArgsAccumulator<'_>,
-    ) -> CodegenResult<(u32, Option<usize>)>
-    where
-        I: IntoIterator<Item = &'a ir::AbiParam>;
+        args: ArgsAccumulator,
+    ) -> CodegenResult<(u32, Option<usize>)>;
 
     /// Returns the offset from FP to the argument area, i.e., jumping over the saved FP, return
     /// address, and maybe other standard elements depending on ABI (e.g. Wasm TLS reg).
@@ -827,8 +825,13 @@ impl SigSet {
         sig: &ir::Signature,
         flags: &settings::Flags,
     ) -> CodegenResult<SigData> {
-        let sret = missing_struct_return(sig);
-        let returns = sret.as_ref().into_iter().chain(&sig.returns);
+        use std::borrow::Cow;
+
+        let returns = if let Some(sret) = missing_struct_return(sig) {
+            Cow::from_iter(std::iter::once(&sret).chain(&sig.returns).copied())
+        } else {
+            Cow::from(sig.returns.as_slice())
+        };
 
         // Compute args and retvals from signature. Handle retvals first,
         // because we may need to add a return-area arg to the args.
@@ -839,7 +842,7 @@ impl SigSet {
         let (sized_stack_ret_space, _) = M::compute_arg_locs(
             sig.call_conv,
             flags,
-            returns,
+            &returns,
             ArgsOrRets::Rets,
             /* extra ret-area ptr = */ false,
             ArgsAccumulator::new(&mut self.abi_args),
diff --git a/cranelift/codegen/src/nan_canonicalization.rs b/cranelift/codegen/src/nan_canonicalization.rs
index bb791d60b..c1bc16f77 100644
--- a/cranelift/codegen/src/nan_canonicalization.rs
+++ b/cranelift/codegen/src/nan_canonicalization.rs
@@ -38,6 +38,10 @@ fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
                 || opcode == Opcode::Nearest
                 || opcode == Opcode::Sqrt
                 || opcode == Opcode::Trunc
+                || opcode == Opcode::Fdemote
+                || opcode == Opcode::Fpromote
+                || opcode == Opcode::FvpromoteLow
+                || opcode == Opcode::Fvdemote
         }
         InstructionData::Binary { opcode, .. } => {
             opcode == Opcode::Fadd
@@ -48,24 +52,6 @@ fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
                 || opcode == Opcode::Fsub
         }
         InstructionData::Ternary { opcode, .. } => opcode == Opcode::Fma,
-
-        // bitcasts coming from arbitrary integers need canonicalization as the
-        // integer isn't known to necessarily be a canonical NaN
-        InstructionData::LoadNoOffset {
-            opcode: Opcode::Bitcast,
-            arg,
-            flags: _,
-        } => {
-            let ret = pos.func.dfg.first_result(inst);
-            let ret_type = pos.func.dfg.value_type(ret);
-            let arg_type = pos.func.dfg.value_type(arg);
-            match (arg_type, ret_type) {
-                (types::I32, types::F32) => true,
-                (types::I64, types::F64) => true,
-                _ => false,
-            }
-        }
-
         _ => false,
     }
 }
diff --git a/cranelift/codegen/src/opts/spectre.isle b/cranelift/codegen/src/opts/spectre.isle
new file mode 100644
index 000000000..c58e29eaa
--- /dev/null
+++ b/cranelift/codegen/src/opts/spectre.isle
@@ -0,0 +1,14 @@
+;; Rewrites for `select_spectre_guard`. Check these rules carefully! This
+;; instruction prohibits all speculation on the controlling value when
+;; determining which input to use as the result. Rewrites must respect that
+;; requirement.
+
+;; If we statically know which value will be the result, it's safe to just use
+;; that value. No speculation on the controlling value is possible if we simply
+;; don't depend on that value at all.
+(rule (simplify (select_spectre_guard _ _ x x))
+      (subsume x))
+(rule (simplify (select_spectre_guard _ (iconst_u _ (u64_nonzero _)) x _))
+      (subsume x))
+(rule (simplify (select_spectre_guard _ (iconst_u _ 0) _ y))
+      (subsume y))
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index 6104a9411..cf2b96f15 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -1037,6 +1037,87 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
             .get(&func_index)
             .map(|s| *s)
     }
+
+    /// Proof-carrying code: create a memtype describing an empty
+    /// runtime struct (to be updated later).
+    fn create_empty_struct_memtype(&self, func: &mut ir::Function) -> ir::MemoryType {
+        func.create_memory_type(ir::MemoryTypeData::Struct {
+            size: 0,
+            fields: vec![],
+        })
+    }
+
+    /// Proof-carrying code: add a new field to a memtype used to
+    /// describe a runtime struct. A memory region of type `memtype`
+    /// will have a pointer at `offset` pointing to another memory
+    /// region of type `pointee`. `readonly` indicates whether the
+    /// PCC-checked code is expected to update this field or not.
+    fn add_field_to_memtype(
+        &self,
+        func: &mut ir::Function,
+        memtype: ir::MemoryType,
+        offset: u32,
+        pointee: ir::MemoryType,
+        readonly: bool,
+    ) {
+        let ptr_size = self.pointer_type().bytes();
+        match &mut func.memory_types[memtype] {
+            ir::MemoryTypeData::Struct { size, fields } => {
+                *size = std::cmp::max(*size, offset.checked_add(ptr_size).unwrap().into());
+                fields.push(ir::MemoryTypeField {
+                    ty: self.pointer_type(),
+                    offset: offset.into(),
+                    readonly,
+                    fact: Some(ir::Fact::Mem {
+                        ty: pointee,
+                        min_offset: 0,
+                        max_offset: 0,
+                        nullable: false,
+                    }),
+                });
+
+                // Sort fields by offset -- we need to do this now
+                // because we may create an arbitrary number of
+                // memtypes for imported memories and we don't
+                // otherwise track them.
+                fields.sort_by_key(|f| f.offset);
+            }
+            _ => panic!("Cannot add field to non-struct memtype"),
+        }
+    }
+
+    /// Add one level of indirection to a pointer-and-memtype pair:
+    /// generate a load in the code at the specified offset, and if
+    /// memtypes are in use, add a field to the original struct and
+    /// generate a new memtype for the pointee.
+    fn load_pointer_with_memtypes(
+        &self,
+        func: &mut ir::Function,
+        value: ir::GlobalValue,
+        offset: u32,
+        readonly: bool,
+        memtype: Option<ir::MemoryType>,
+    ) -> (ir::GlobalValue, Option<ir::MemoryType>) {
+        let pointee = func.create_global_value(ir::GlobalValueData::Load {
+            base: value,
+            offset: Offset32::new(i32::try_from(offset).unwrap()),
+            global_type: self.pointer_type(),
+            flags: MemFlags::trusted().with_readonly(),
+        });
+
+        let mt = memtype.map(|mt| {
+            let pointee_mt = self.create_empty_struct_memtype(func);
+            self.add_field_to_memtype(func, mt, offset, pointee_mt, readonly);
+            func.global_value_facts[pointee] = Some(Fact::Mem {
+                ty: pointee_mt,
+                min_offset: 0,
+                max_offset: 0,
+                nullable: false,
+            });
+            pointee_mt
+        });
+        (pointee, mt)
+    }
 }
 
 struct Call<'a, 'func, 'module_env> {
@@ -2037,16 +2118,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                     // VMMemoryDefinition` to it and dereference that when
                     // atomically growing it.
                     let from_offset = self.offsets.vmctx_vmmemory_pointer(def_index);
-                    let memory = func.create_global_value(ir::GlobalValueData::Load {
-                        base: vmctx,
-                        offset: Offset32::new(i32::try_from(from_offset).unwrap()),
-                        global_type: pointer_type,
-                        flags: MemFlags::trusted().with_readonly(),
-                    });
+                    let (memory, def_mt) = self.load_pointer_with_memtypes(
+                        func,
+                        vmctx,
+                        from_offset,
+                        true,
+                        self.pcc_vmctx_memtype,
+                    );
                     let base_offset = i32::from(self.offsets.ptr.vmmemory_definition_base());
                     let current_length_offset =
                         i32::from(self.offsets.ptr.vmmemory_definition_current_length());
-                    (memory, base_offset, current_length_offset, None)
+                    (memory, base_offset, current_length_offset, def_mt)
                 } else {
                     let owned_index = self.module.owned_memory_index(def_index);
                     let owned_base_offset =
@@ -2065,16 +2147,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                 }
             } else {
                 let from_offset = self.offsets.vmctx_vmmemory_import_from(index);
-                let memory = func.create_global_value(ir::GlobalValueData::Load {
-                    base: vmctx,
-                    offset: Offset32::new(i32::try_from(from_offset).unwrap()),
-                    global_type: pointer_type,
-                    flags: MemFlags::trusted().with_readonly(),
-                });
+                let (memory, def_mt) = self.load_pointer_with_memtypes(
+                    func,
+                    vmctx,
+                    from_offset,
+                    true,
+                    self.pcc_vmctx_memtype,
+                );
                 let base_offset = i32::from(self.offsets.ptr.vmmemory_definition_base());
                 let current_length_offset =
                     i32::from(self.offsets.ptr.vmmemory_definition_current_length());
-                (memory, base_offset, current_length_offset, None)
+                (memory, base_offset, current_length_offset, def_mt)
             }
         };
 
@@ -2140,7 +2223,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                                 );
                                 *size = std::cmp::max(*size, fields_end);
                             }
-                            _ => {}
+                            _ => {
+                                panic!("Bad memtype");
+                            }
                         }
                         // Apply a fact to the base pointer.
                         (Some(base_fact), Some(data_mt))
@@ -2199,7 +2284,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
                                     offset + u64::from(self.isa.pointer_type().bytes()),
                                 );
                             }
-                            _ => {}
+                            _ => {
+                                panic!("Bad memtype");
+                            }
                         }
                         // Apply a fact to the base pointer.
                         (Some(base_fact), Some(data_mt))
@@ -2829,16 +2916,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         if self.tunables.consume_fuel && state.reachable() {
             self.fuel_function_exit(builder);
         }
-        if let Some(pcc_vmctx_memtype) = self.pcc_vmctx_memtype {
-            // Sort the fields by offset in the struct definition for
-            // vmctx, now that we've completed it.
-            match &mut builder.func.memory_types[pcc_vmctx_memtype] {
-                ir::MemoryTypeData::Struct { fields, .. } => {
-                    fields.sort_by_key(|f| f.offset);
-                }
-                _ => {}
-            }
-        }
         Ok(())
     }
 
diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs
index c974af2d7..d54202112 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -342,7 +342,6 @@ impl Config {
     pub fn disable_unimplemented_winch_proposals(&mut self) {
         self.module_config.config.simd_enabled = false;
         self.module_config.config.relaxed_simd_enabled = false;
-        self.module_config.config.memory64_enabled = false;
         self.module_config.config.gc_enabled = false;
         self.module_config.config.threads_enabled = false;
         self.module_config.config.tail_call_enabled = false;
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 06855d307..92003e8b3 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -521,15 +521,6 @@ impl CallThreadState {
     }
 }
 
-struct ResetCell<'a, T: Copy>(&'a Cell<T>, T);
-
-impl<T: Copy> Drop for ResetCell<'_, T> {
-    #[inline]
-    fn drop(&mut self) {
-        self.0.set(self.1);
-    }
-}
-
 // A private inner module for managing the TLS state that we require across
 // calls in wasm. The WebAssembly code is called from C++ and then a trap may
 // happen which requires us to read some contextual state to figure out what to
diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs
index 4e9f3578e..98233fa45 100644
--- a/crates/wasi/src/ctx.rs
+++ b/crates/wasi/src/ctx.rs
@@ -30,6 +30,7 @@ pub struct WasiCtxBuilder {
     wall_clock: Box<dyn HostWallClock + Send>,
     monotonic_clock: Box<dyn HostMonotonicClock + Send>,
     allowed_network_uses: AllowedNetworkUses,
+    allow_blocking_current_thread: bool,
     built: bool,
 }
 
@@ -80,6 +81,7 @@ impl WasiCtxBuilder {
             wall_clock: wall_clock(),
             monotonic_clock: monotonic_clock(),
             allowed_network_uses: AllowedNetworkUses::default(),
+            allow_blocking_current_thread: false,
             built: false,
         }
     }
@@ -115,6 +117,37 @@ impl WasiCtxBuilder {
         self.inherit_stdin().inherit_stdout().inherit_stderr()
     }
 
+    /// Configures whether or not blocking operations made through this
+    /// `WasiCtx` are allowed to block the current thread.
+    ///
+    /// WASI is currently implemented on top of the Rust
+    /// [Tokio](https://tokio.rs/) library. While most WASI APIs are
+    /// non-blocking some are instead blocking from the perspective of
+    /// WebAssembly. For example opening a file is a blocking operation with
+    /// respect to WebAssembly but it's implemented as an asynchronous operation
+    /// on the host. This is currently done with Tokio's
+    /// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).
+    ///
+    /// When WebAssembly is used in a synchronous context, for example when
+    /// [`Config::async_support`] is disabled, then this asynchronous operation
+    /// is quickly turned back into a synchronous operation with a `block_on` in
+    /// Rust. This switching back-and-forth between a blocking a non-blocking
+    /// context can have overhead, and this option exists to help alleviate this
+    /// overhead.
+    ///
+    /// This option indicates that for WASI functions that are blocking from the
+    /// perspective of WebAssembly it's ok to block the native thread as well.
+    /// This means that this back-and-forth between async and sync won't happen
+    /// and instead blocking operations are performed on-thread (such as opening
+    /// a file). This can improve the performance of WASI operations when async
+    /// support is disabled.
+    ///
+    /// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support
+    pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self {
+        self.allow_blocking_current_thread = enable;
+        self
+    }
+
     pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
         self.env.extend(
             env.iter()
@@ -162,7 +195,13 @@ impl WasiCtxBuilder {
             open_mode |= OpenMode::WRITE;
         }
         self.preopens.push((
-            Dir::new(dir, perms, file_perms, open_mode),
+            Dir::new(
+                dir,
+                perms,
+                file_perms,
+                open_mode,
+                self.allow_blocking_current_thread,
+            ),
             path.as_ref().to_owned(),
         ));
         self
@@ -263,6 +302,7 @@ impl WasiCtxBuilder {
             wall_clock,
             monotonic_clock,
             allowed_network_uses,
+            allow_blocking_current_thread,
             built: _,
         } = mem::replace(self, Self::new());
         self.built = true;
@@ -281,6 +321,7 @@ impl WasiCtxBuilder {
             wall_clock,
             monotonic_clock,
             allowed_network_uses,
+            allow_blocking_current_thread,
         }
     }
 
@@ -310,6 +351,7 @@ pub struct WasiCtx {
     pub(crate) stderr: Box<dyn StdoutStream>,
     pub(crate) socket_addr_check: SocketAddrCheck,
     pub(crate) allowed_network_uses: AllowedNetworkUses,
+    pub(crate) allow_blocking_current_thread: bool,
 }
 
 pub struct AllowedNetworkUses {
diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs
index 86bd9398d..3dffa4603 100644
--- a/crates/wasi/src/filesystem.rs
+++ b/crates/wasi/src/filesystem.rs
@@ -74,6 +74,7 @@ bitflags::bitflags! {
     }
 }
 
+#[derive(Clone)]
 pub struct File {
     /// The operating system File this struct is mediating access to.
     ///
@@ -92,14 +93,22 @@ pub struct File {
     /// doesn't presently provide a cross-platform equivelant of reading the
     /// oflags back out using fcntl.
     pub open_mode: OpenMode,
+
+    allow_blocking_current_thread: bool,
 }
 
 impl File {
-    pub fn new(file: cap_std::fs::File, perms: FilePerms, open_mode: OpenMode) -> Self {
+    pub fn new(
+        file: cap_std::fs::File,
+        perms: FilePerms,
+        open_mode: OpenMode,
+        allow_blocking_current_thread: bool,
+    ) -> Self {
         Self {
             file: Arc::new(file),
             perms,
             open_mode,
+            allow_blocking_current_thread,
         }
     }
 
@@ -110,11 +119,31 @@ impl File {
         F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
         R: Send + 'static,
     {
-        let f = self.file.clone();
-        spawn_blocking(move || body(&f)).await
+        match self._spawn_blocking(body) {
+            SpawnBlocking::Done(result) => result,
+            SpawnBlocking::Spawned(task) => task.await,
+        }
+    }
+
+    fn _spawn_blocking<F, R>(&self, body: F) -> SpawnBlocking<R>
+    where
+        F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        if self.allow_blocking_current_thread {
+            SpawnBlocking::Done(body(&self.file))
+        } else {
+            let f = self.file.clone();
+            SpawnBlocking::Spawned(spawn_blocking(move || body(&f)))
+        }
     }
 }
 
+enum SpawnBlocking<T> {
+    Done(T),
+    Spawned(AbortOnDropJoinHandle<T>),
+}
+
 bitflags::bitflags! {
     #[derive(Copy, Clone, Debug, PartialEq, Eq)]
     pub struct DirPerms: usize {
@@ -146,6 +175,8 @@ pub struct Dir {
     /// doesn't presently provide a cross-platform equivelant of reading the
     /// oflags back out using fcntl.
     pub open_mode: OpenMode,
+
+    allow_blocking_current_thread: bool,
 }
 
 impl Dir {
@@ -154,12 +185,14 @@ impl Dir {
         perms: DirPerms,
         file_perms: FilePerms,
         open_mode: OpenMode,
+        allow_blocking_current_thread: bool,
     ) -> Self {
         Dir {
             dir: Arc::new(dir),
             perms,
             file_perms,
             open_mode,
+            allow_blocking_current_thread,
         }
     }
 
@@ -170,30 +203,38 @@ impl Dir {
         F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
         R: Send + 'static,
     {
-        let d = self.dir.clone();
-        spawn_blocking(move || body(&d)).await
+        if self.allow_blocking_current_thread {
+            body(&self.dir)
+        } else {
+            let d = self.dir.clone();
+            spawn_blocking(move || body(&d)).await
+        }
     }
 }
 
 pub struct FileInputStream {
-    file: Arc<cap_std::fs::File>,
+    file: File,
     position: u64,
 }
 impl FileInputStream {
-    pub fn new(file: Arc<cap_std::fs::File>, position: u64) -> Self {
-        Self { file, position }
+    pub fn new(file: &File, position: u64) -> Self {
+        Self {
+            file: file.clone(),
+            position,
+        }
     }
 
     pub async fn read(&mut self, size: usize) -> Result<Bytes, StreamError> {
         use system_interface::fs::FileIoExt;
-        let f = Arc::clone(&self.file);
         let p = self.position;
-        let (r, mut buf) = spawn_blocking(move || {
-            let mut buf = BytesMut::zeroed(size);
-            let r = f.read_at(&mut buf, p);
-            (r, buf)
-        })
-        .await;
+        let (r, mut buf) = self
+            .file
+            .spawn_blocking(move |f| {
+                let mut buf = BytesMut::zeroed(size);
+                let r = f.read_at(&mut buf, p);
+                (r, buf)
+            })
+            .await;
         let n = read_result(r)?;
         buf.truncate(n);
         self.position += n as u64;
@@ -222,7 +263,7 @@ pub(crate) enum FileOutputMode {
 }
 
 pub(crate) struct FileOutputStream {
-    file: Arc<cap_std::fs::File>,
+    file: File,
     mode: FileOutputMode,
     state: OutputState,
 }
@@ -238,16 +279,17 @@ enum OutputState {
 }
 
 impl FileOutputStream {
-    pub fn write_at(file: Arc<cap_std::fs::File>, position: u64) -> Self {
+    pub fn write_at(file: &File, position: u64) -> Self {
         Self {
-            file,
+            file: file.clone(),
             mode: FileOutputMode::Position(position),
             state: OutputState::Ready,
         }
     }
-    pub fn append(file: Arc<cap_std::fs::File>) -> Self {
+
+    pub fn append(file: &File) -> Self {
         Self {
-            file,
+            file: file.clone(),
             mode: FileOutputMode::Append,
             state: OutputState::Ready,
         }
@@ -275,33 +317,43 @@ impl HostOutputStream for FileOutputStream {
             return Ok(());
         }
 
-        let f = Arc::clone(&self.file);
         let m = self.mode;
-        let task = spawn_blocking(move || match m {
-            FileOutputMode::Position(mut p) => {
-                let mut total = 0;
-                let mut buf = buf;
-                while !buf.is_empty() {
-                    let nwritten = f.write_at(buf.as_ref(), p)?;
-                    // afterwards buf contains [nwritten, len):
-                    let _ = buf.split_to(nwritten);
-                    p += nwritten as u64;
-                    total += nwritten;
+        let result = self.file._spawn_blocking(move |f| {
+            match m {
+                FileOutputMode::Position(mut p) => {
+                    let mut total = 0;
+                    let mut buf = buf;
+                    while !buf.is_empty() {
+                        let nwritten = f.write_at(buf.as_ref(), p)?;
+                        // afterwards buf contains [nwritten, len):
+                        let _ = buf.split_to(nwritten);
+                        p += nwritten as u64;
+                        total += nwritten;
+                    }
+                    Ok(total)
                 }
-                Ok(total)
-            }
-            FileOutputMode::Append => {
-                let mut total = 0;
-                let mut buf = buf;
-                while !buf.is_empty() {
-                    let nwritten = f.append(buf.as_ref())?;
-                    let _ = buf.split_to(nwritten);
-                    total += nwritten;
+                FileOutputMode::Append => {
+                    let mut total = 0;
+                    let mut buf = buf;
+                    while !buf.is_empty() {
+                        let nwritten = f.append(buf.as_ref())?;
+                        let _ = buf.split_to(nwritten);
+                        total += nwritten;
+                    }
+                    Ok(total)
                 }
-                Ok(total)
             }
         });
-        self.state = OutputState::Waiting(task);
+        self.state = match result {
+            SpawnBlocking::Done(Ok(nwritten)) => {
+                if let FileOutputMode::Position(ref mut p) = &mut self.mode {
+                    *p += nwritten as u64;
+                }
+                OutputState::Ready
+            }
+            SpawnBlocking::Done(Err(e)) => OutputState::Error(e),
+            SpawnBlocking::Spawned(task) => OutputState::Waiting(task),
+        };
         Ok(())
     }
     fn flush(&mut self) -> Result<(), StreamError> {
diff --git a/crates/wasi/src/host/filesystem.rs b/crates/wasi/src/host/filesystem.rs
index 6094bddb7..5ad40071d 100644
--- a/crates/wasi/src/host/filesystem.rs
+++ b/crates/wasi/src/host/filesystem.rs
@@ -493,6 +493,7 @@ impl<T: WasiView> HostDescriptor for T {
         use system_interface::fs::{FdFlags, GetSetFdFlags};
         use types::{DescriptorFlags, OpenFlags};
 
+        let allow_blocking_current_thread = self.ctx().allow_blocking_current_thread;
         let table = self.table();
         let d = table.get(&fd)?.dir()?;
         if !d.perms.contains(DirPerms::READ) {
@@ -609,11 +610,15 @@ impl<T: WasiView> HostDescriptor for T {
                 d.perms,
                 d.file_perms,
                 open_mode,
+                allow_blocking_current_thread,
             )))?),
 
-            OpenResult::File(file) => {
-                Ok(table.push(Descriptor::File(File::new(file, d.file_perms, open_mode)))?)
-            }
+            OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new(
+                file,
+                d.file_perms,
+                open_mode,
+                allow_blocking_current_thread,
+            )))?),
 
             OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()),
         }
@@ -730,11 +735,9 @@ impl<T: WasiView> HostDescriptor for T {
         if !f.perms.contains(FilePerms::READ) {
             Err(types::ErrorCode::BadDescriptor)?;
         }
-        // Duplicate the file descriptor so that we get an indepenent lifetime.
-        let clone = std::sync::Arc::clone(&f.file);
 
         // Create a stream view for it.
-        let reader = FileInputStream::new(clone, offset);
+        let reader = FileInputStream::new(f, offset);
 
         // Insert the stream view into the table. Trap if the table is full.
         let index = self.table().push(InputStream::File(reader))?;
@@ -754,11 +757,8 @@ impl<T: WasiView> HostDescriptor for T {
             Err(types::ErrorCode::BadDescriptor)?;
         }
 
-        // Duplicate the file descriptor so that we get an indepenent lifetime.
-        let clone = std::sync::Arc::clone(&f.file);
-
         // Create a stream view for it.
-        let writer = FileOutputStream::write_at(clone, offset);
+        let writer = FileOutputStream::write_at(f, offset);
         let writer: OutputStream = Box::new(writer);
 
         // Insert the stream view into the table. Trap if the table is full.
@@ -777,11 +777,9 @@ impl<T: WasiView> HostDescriptor for T {
         if !f.perms.contains(FilePerms::WRITE) {
             Err(types::ErrorCode::BadDescriptor)?;
         }
-        // Duplicate the file descriptor so that we get an indepenent lifetime.
-        let clone = std::sync::Arc::clone(&f.file);
 
         // Create a stream view for it.
-        let appender = FileOutputStream::append(clone);
+        let appender = FileOutputStream::append(f);
         let appender: OutputStream = Box::new(appender);
 
         // Insert the stream view into the table. Trap if the table is full.
diff --git a/crates/wasmtime/src/compile.rs b/crates/wasmtime/src/compile.rs
index b8811619d..9f108671c 100644
--- a/crates/wasmtime/src/compile.rs
+++ b/crates/wasmtime/src/compile.rs
@@ -38,6 +38,9 @@ use wasmtime_environ::{
     WasmFunctionInfo,
 };
 
+mod code_builder;
+pub use self::code_builder::{CodeBuilder, HashedEngineCompileEnv};
+
 /// Converts an input binary-encoded WebAssembly module to compilation
 /// artifacts and type information.
 ///
@@ -113,7 +116,7 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
 pub(crate) fn build_component_artifacts<T: FinishedObject>(
     engine: &Engine,
     binary: &[u8],
-) -> Result<(T, wasmtime_environ::component::ComponentArtifacts)> {
+) -> Result<(T, Option<wasmtime_environ::component::ComponentArtifacts>)> {
     use wasmtime_environ::component::{CompiledComponentInfo, ComponentArtifacts};
     use wasmtime_environ::ScopeVec;
 
@@ -179,7 +182,7 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
     object.serialize_info(&artifacts);
 
     let result = T::finish_object(object)?;
-    Ok((result, artifacts))
+    Ok((result, Some(artifacts)))
 }
 
 type CompileInput<'a> = Box<dyn FnOnce(&dyn Compiler) -> Result<CompileOutput> + Send + 'a>;
diff --git a/crates/wasmtime/src/compile/code_builder.rs b/crates/wasmtime/src/compile/code_builder.rs
new file mode 100644
index 000000000..fb026d2a1
--- /dev/null
+++ b/crates/wasmtime/src/compile/code_builder.rs
@@ -0,0 +1,296 @@
+#[cfg(all(feature = "runtime", feature = "component-model"))]
+use crate::component::Component;
+use crate::Engine;
+#[cfg(feature = "runtime")]
+use crate::{instantiate::MmapVecWrapper, CodeMemory, Module};
+use anyhow::{anyhow, bail, Context, Result};
+use std::borrow::Cow;
+use std::path::Path;
+use std::sync::Arc;
+use wasmtime_environ::ObjectKind;
+#[cfg(feature = "runtime")]
+use wasmtime_runtime::MmapVec;
+
+/// Builder-style structure used to create a [`Module`](crate::Module) or
+/// pre-compile a module to a serialized list of bytes.
+///
+/// This structure can be used for more advanced configuration when compiling a
+/// WebAssembly module. Most configuration can use simpler constructors such as:
+///
+/// * [`Module::new`](crate::Module::new)
+/// * [`Module::from_file`](crate::Module::from_file)
+/// * [`Module::from_binary`](crate::Module::from_binary)
+///
+/// Note that a [`CodeBuilder`] always involves compiling WebAssembly bytes
+/// to machine code. To deserialize a list of bytes use
+/// [`Module::deserialize`](crate::Module::deserialize) instead.
+///
+/// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured
+/// before calling [`compile_module_serialized`] or [`compile_module`]. This can be
+/// provided with either the [`wasm`] or [`wasm_file`] method. Note that only
+/// a single source of bytes can be provided.
+///
+/// # WebAssembly Text Format
+///
+/// This builder supports the WebAssembly Text Format (`*.wat` files).
+/// WebAssembly text files are automatically converted to a WebAssembly binary
+/// and then the binary is compiled. This requires the `wat` feature of the
+/// `wasmtime` crate to be enabled, and the feature is enabled by default.
+///
+/// If the text format is not desired then the [`CodeBuilder::wat`] method
+/// can be used to disable this conversion.
+///
+/// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
+/// [`compile_module`]: CodeBuilder::compile_module
+/// [`wasm`]: CodeBuilder::wasm
+/// [`wasm_file`]: CodeBuilder::wasm_file
+pub struct CodeBuilder<'a> {
+    engine: &'a Engine,
+    wasm: Option<Cow<'a, [u8]>>,
+    wasm_path: Option<Cow<'a, Path>>,
+    wat: bool,
+}
+
+impl<'a> CodeBuilder<'a> {
+    /// Creates a new builder which will insert modules into the specified
+    /// [`Engine`].
+    pub fn new(engine: &'a Engine) -> CodeBuilder<'a> {
+        CodeBuilder {
+            engine,
+            wasm: None,
+            wasm_path: None,
+            wat: cfg!(feature = "wat"),
+        }
+    }
+
+    /// Configures the WebAssembly binary or text that is being compiled.
+    ///
+    /// The `wasm_bytes` parameter is either a binary WebAssembly file or a
+    /// WebAssembly module in its text format. This will be stored within the
+    /// [`CodeBuilder`] for processing later when compilation is finalized.
+    ///
+    /// The optional `wasm_path` parameter is the path to the `wasm_bytes` on
+    /// disk, if any. This may be used for diagnostics and other
+    /// debugging-related purposes, but this method will not read the path
+    /// specified.
+    ///
+    /// # Errors
+    ///
+    /// If wasm bytes have already been configured via a call to this method or
+    /// [`CodeBuilder::wasm_file`] then an error will be returned.
+    pub fn wasm(&mut self, wasm_bytes: &'a [u8], wasm_path: Option<&'a Path>) -> Result<&mut Self> {
+        if self.wasm.is_some() {
+            bail!("cannot call `wasm` or `wasm_file` twice");
+        }
+        self.wasm = Some(wasm_bytes.into());
+        self.wasm_path = wasm_path.map(|p| p.into());
+        Ok(self)
+    }
+
+    /// Configures whether the WebAssembly text format is supported in this
+    /// builder.
+    ///
+    /// This support is enabled by default if the `wat` crate feature is also
+    /// enabled.
+    ///
+    /// # Errors
+    ///
+    /// If this feature is explicitly enabled here via this method and the
+    /// `wat` crate feature is disabled then an error will be returned.
+    pub fn wat(&mut self, enable: bool) -> Result<&mut Self> {
+        if !cfg!(feature = "wat") && enable {
+            bail!("support for `wat` was disabled at compile time");
+        }
+        self.wat = enable;
+        Ok(self)
+    }
+
+    /// Reads the `file` specified for the WebAssembly bytes that are going to
+    /// be compiled.
+    ///
+    /// This method will read `file` from the filesystem and interpret it
+    /// either as a WebAssembly binary or as a WebAssembly text file. The
+    /// contents are inspected to do this, the file extension is not consulted.
+    ///
+    /// # Errors
+    ///
+    /// If wasm bytes have already been configured via a call to this method or
+    /// [`CodeBuilder::wasm`] then an error will be returned.
+    ///
+    /// If `file` can't be read or an error happens reading it then that will
+    /// also be returned.
+    pub fn wasm_file(&mut self, file: &'a Path) -> Result<&mut Self> {
+        if self.wasm.is_some() {
+            bail!("cannot call `wasm` or `wasm_file` twice");
+        }
+        let wasm = std::fs::read(file)
+            .with_context(|| format!("failed to read input file: {}", file.display()))?;
+        self.wasm = Some(wasm.into());
+        self.wasm_path = Some(file.into());
+        Ok(self)
+    }
+
+    fn wasm_binary(&self) -> Result<Cow<'_, [u8]>> {
+        let wasm = self
+            .wasm
+            .as_ref()
+            .ok_or_else(|| anyhow!("no wasm bytes have been configured"))?;
+        if self.wat {
+            #[cfg(feature = "wat")]
+            return wat::parse_bytes(wasm).map_err(|mut e| {
+                if let Some(path) = &self.wasm_path {
+                    e.set_path(path);
+                }
+                e.into()
+            });
+        }
+        Ok((&wasm[..]).into())
+    }
+
+    #[cfg(feature = "runtime")]
+    fn compile_cached<T>(
+        &self,
+        build_artifacts: fn(&Engine, &[u8]) -> Result<(MmapVecWrapper, Option<T>)>,
+    ) -> Result<(Arc<CodeMemory>, Option<T>)> {
+        let wasm = self.wasm_binary()?;
+
+        self.engine
+            .check_compatible_with_native_host()
+            .context("compilation settings are not compatible with the native host")?;
+
+        #[cfg(feature = "cache")]
+        {
+            let state = (
+                HashedEngineCompileEnv(self.engine),
+                &wasm,
+                // Don't hash this as it's just its own "pure" function pointer.
+                NotHashed(build_artifacts),
+            );
+            let (code, info_and_types) =
+                wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache_config())
+                    .get_data_raw(
+                        &state,
+                        // Cache miss, compute the actual artifacts
+                        |(engine, wasm, build_artifacts)| -> Result<_> {
+                            let (mmap, info) = (build_artifacts.0)(engine.0, wasm)?;
+                            let code = publish_mmap(mmap.0)?;
+                            Ok((code, info))
+                        },
+                        // Implementation of how to serialize artifacts
+                        |(_engine, _wasm, _), (code, _info_and_types)| Some(code.mmap().to_vec()),
+                        // Cache hit, deserialize the provided artifacts
+                        |(engine, _wasm, _), serialized_bytes| {
+                            let code = engine
+                                .0
+                                .load_code_bytes(&serialized_bytes, ObjectKind::Module)
+                                .ok()?;
+                            Some((code, None))
+                        },
+                    )?;
+            return Ok((code, info_and_types));
+        }
+
+        #[cfg(not(feature = "cache"))]
+        {
+            let (mmap, info_and_types) = build_artifacts(self.engine, &wasm)?;
+            let code = publish_mmap(mmap.0)?;
+            return Ok((code, info_and_types));
+        }
+
+        struct NotHashed<T>(T);
+
+        impl<T> std::hash::Hash for NotHashed<T> {
+            fn hash<H: std::hash::Hasher>(&self, _hasher: &mut H) {}
+        }
+    }
+
+    /// Finishes this compilation and produces a serialized list of bytes.
+    ///
+    /// This method requires that either [`CodeBuilder::wasm`] or
+    /// [`CodeBuilder::wasm_file`] was invoked prior to indicate what is
+    /// being compiled.
+    ///
+    /// This method will block the current thread until compilation has
+    /// finished, and when done the serialized artifact will be returned.
+    ///
+    /// Note that this method will never cache compilations, even if the
+    /// `cache` feature is enabled.
+    ///
+    /// # Errors
+    ///
+    /// This can fail if the input wasm module was not valid or if another
+    /// compilation-related error is encountered.
+    pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
+        let wasm = self.wasm_binary()?;
+        let (v, _) = super::build_artifacts(self.engine, &wasm)?;
+        Ok(v)
+    }
+
+    /// Same as [`CodeBuilder::compile_module_serialized`] except that a
+    /// [`Module`](crate::Module) is produced instead.
+    ///
+    /// Note that this method will cache compilations if the `cache` feature is
+    /// enabled and turned on in [`Config`](crate::Config).
+    #[cfg(feature = "runtime")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
+    pub fn compile_module(&self) -> Result<Module> {
+        let (code, info_and_types) = self.compile_cached(super::build_artifacts)?;
+        Module::from_parts(self.engine, code, info_and_types)
+    }
+
+    /// Same as [`CodeBuilder::compile_module_serialized`] except that it
+    /// compiles a serialized [`Component`] instead of a module.
+    #[cfg(feature = "component-model")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "component-model")))]
+    pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
+        let bytes = self.wasm_binary()?;
+        let (v, _) = super::build_component_artifacts(self.engine, &bytes)?;
+        Ok(v)
+    }
+
+    /// Same as [`CodeBuilder::compile_module`] except that it compiles a
+    /// [`Component`] instead of a module.
+    #[cfg(all(feature = "runtime", feature = "component-model"))]
+    #[cfg_attr(
+        docsrs,
+        doc(cfg(all(feature = "runtime", feature = "component-model")))
+    )]
+    pub fn compile_component(&self) -> Result<Component> {
+        let (code, artifacts) = self.compile_cached(super::build_component_artifacts)?;
+        Component::from_parts(self.engine, code, artifacts)
+    }
+}
+
+/// This is a helper struct used when caching to hash the state of an `Engine`
+/// used for module compilation.
+///
+/// The hash computed for this structure is used to key the global wasmtime
+/// cache and dictates whether artifacts are reused. Consequently the contents
+/// of this hash dictate when artifacts are or aren't re-used.
+pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
+
+impl std::hash::Hash for HashedEngineCompileEnv<'_> {
+    fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
+        // Hash the compiler's state based on its target and configuration.
+        let compiler = self.0.compiler();
+        compiler.triple().hash(hasher);
+        compiler.flags().hash(hasher);
+        compiler.isa_flags().hash(hasher);
+
+        // Hash configuration state read for compilation
+        let config = self.0.config();
+        self.0.tunables().hash(hasher);
+        config.features.hash(hasher);
+        config.wmemcheck.hash(hasher);
+
+        // Catch accidental bugs of reusing across crate versions.
+        config.module_version.hash(hasher);
+    }
+}
+
+#[cfg(feature = "runtime")]
+fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
+    let mut code = CodeMemory::new(mmap)?;
+    code.publish()?;
+    Ok(Arc::new(code))
+}
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index bef3e7061..271c9bc25 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -1009,9 +1009,14 @@ impl Config {
     /// Configures whether Cranelift should perform a NaN-canonicalization pass.
     ///
     /// When Cranelift is used as a code generation backend this will configure
-    /// it to replace NaNs with a single canonical value. This is useful for users
-    /// requiring entirely deterministic WebAssembly computation.
-    /// This is not required by the WebAssembly spec, so it is not enabled by default.
+    /// it to replace NaNs with a single canonical value. This is useful for
+    /// users requiring entirely deterministic WebAssembly computation.  This is
+    /// not required by the WebAssembly spec, so it is not enabled by default.
+    ///
+    /// Note that this option affects not only WebAssembly's `f32` and `f64`
+    /// types but additionally the `v128` type. This option will cause
+    /// operations using any of these types to have extra checks placed after
+    /// them to normalize NaN values as needed.
     ///
     /// The default value for this is `false`
     #[cfg(any(feature = "cranelift", feature = "winch"))]
diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs
index aa69554a8..0e6f16cf2 100644
--- a/crates/wasmtime/src/engine.rs
+++ b/crates/wasmtime/src/engine.rs
@@ -514,10 +514,9 @@ impl Engine {
     /// [binary]: https://webassembly.github.io/spec/core/binary/index.html
     /// [text]: https://webassembly.github.io/spec/core/text/index.html
     pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
-        #[cfg(feature = "wat")]
-        let bytes = wat::parse_bytes(&bytes)?;
-        let (v, _) = crate::compile::build_artifacts::<Vec<u8>>(self, &bytes)?;
-        Ok(v)
+        crate::CodeBuilder::new(self)
+            .wasm(bytes, None)?
+            .compile_module_serialized()
     }
 
     /// Same as [`Engine::precompile_module`] except for a
@@ -525,10 +524,9 @@ impl Engine {
     #[cfg(feature = "component-model")]
     #[cfg_attr(docsrs, doc(cfg(feature = "component-model")))]
     pub fn precompile_component(&self, bytes: &[u8]) -> Result<Vec<u8>> {
-        #[cfg(feature = "wat")]
-        let bytes = wat::parse_bytes(&bytes)?;
-        let (v, _) = crate::compile::build_component_artifacts::<Vec<u8>>(self, &bytes)?;
-        Ok(v)
+        crate::CodeBuilder::new(self)
+            .wasm(bytes, None)?
+            .compile_component_serialized()
     }
 
     /// Produces a blob of bytes by serializing the `engine`'s configuration data to
@@ -656,7 +654,7 @@ impl Engine {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(feature = "cranelift")))] // see build.rs
     pub fn precompile_compatibility_hash(&self) -> impl std::hash::Hash + '_ {
-        crate::module::HashedEngineCompileEnv(self)
+        crate::compile::HashedEngineCompileEnv(self)
     }
 
     /// Executes `f1` and `f2` in parallel if parallel compilation is enabled at
diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs
index da007574f..78002951c 100644
--- a/crates/wasmtime/src/lib.rs
+++ b/crates/wasmtime/src/lib.rs
@@ -269,6 +269,8 @@ pub use runtime::*;
 
 #[cfg(any(feature = "cranelift", feature = "winch"))]
 mod compile;
+#[cfg(any(feature = "cranelift", feature = "winch"))]
+pub use compile::CodeBuilder;
 
 mod config;
 mod engine;
diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs
index 25444ef6a..4e263ba3f 100644
--- a/crates/wasmtime/src/runtime/component/component.rs
+++ b/crates/wasmtime/src/runtime/component/component.rs
@@ -1,12 +1,11 @@
 use crate::component::matching::InstanceType;
 use crate::component::types;
 use crate::{
-    code::CodeObject, code_memory::CodeMemory, instantiate::MmapVecWrapper,
-    type_registry::TypeCollection, Engine, Module, ResourcesRequired,
+    code::CodeObject, code_memory::CodeMemory, type_registry::TypeCollection, Engine, Module,
+    ResourcesRequired,
 };
 use crate::{FuncType, ValType};
-use anyhow::{bail, Context, Result};
-use std::fs;
+use anyhow::Result;
 use std::mem;
 use std::ops::Range;
 use std::path::Path;
@@ -151,10 +150,9 @@ impl Component {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Component> {
-        let bytes = bytes.as_ref();
-        #[cfg(feature = "wat")]
-        let bytes = wat::parse_bytes(bytes)?;
-        Component::from_binary(engine, &bytes)
+        crate::CodeBuilder::new(engine)
+            .wasm(bytes.as_ref(), None)?
+            .compile_component()
     }
 
     /// Compiles a new WebAssembly component from a wasm file on disk pointed
@@ -165,23 +163,9 @@ impl Component {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Component> {
-        match Self::new(
-            engine,
-            &fs::read(&file).with_context(|| "failed to read input file")?,
-        ) {
-            Ok(m) => Ok(m),
-            Err(e) => {
-                cfg_if::cfg_if! {
-                    if #[cfg(feature = "wat")] {
-                        let mut e = e.downcast::<wat::Error>()?;
-                        e.set_path(file);
-                        bail!(e)
-                    } else {
-                        Err(e)
-                    }
-                }
-            }
-        }
+        crate::CodeBuilder::new(engine)
+            .wasm_file(file.as_ref())?
+            .compile_component()
     }
 
     /// Compiles a new WebAssembly component from the in-memory wasm image
@@ -196,56 +180,10 @@ impl Component {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Component> {
-        use crate::compile::build_component_artifacts;
-        use crate::module::HashedEngineCompileEnv;
-        use wasmtime_runtime::MmapVec;
-
-        engine
-            .check_compatible_with_native_host()
-            .context("compilation settings are not compatible with the native host")?;
-
-        cfg_if::cfg_if! {
-            if #[cfg(feature = "cache")] {
-                let state = (HashedEngineCompileEnv(engine), binary);
-                let (code, artifacts) = wasmtime_cache::ModuleCacheEntry::new(
-                    "wasmtime",
-                    engine.cache_config(),
-                )
-                .get_data_raw(
-                    &state,
-
-                    // Cache miss, compute the actual artifacts
-                    |(engine, wasm)| -> Result<_> {
-                        let (mmap, artifacts) = build_component_artifacts::<MmapVecWrapper>(engine.0, wasm)?;
-                        let code = publish_mmap(mmap.0)?;
-                        Ok((code, Some(artifacts)))
-                    },
-
-                    // Implementation of how to serialize artifacts
-                    |(_engine, _wasm), (code, _info_and_types)| {
-                        Some(code.mmap().to_vec())
-                    },
-
-                    // Cache hit, deserialize the provided artifacts
-                    |(engine, _wasm), serialized_bytes| {
-                        let code = engine.0.load_code_bytes(&serialized_bytes, ObjectKind::Component).ok()?;
-                        Some((code, None))
-                    },
-                )?;
-            } else {
-                let (mmap, artifacts) = build_component_artifacts::<MmapVecWrapper>(engine, binary)?;
-                let artifacts = Some(artifacts);
-                let code = publish_mmap(mmap.0)?;
-            }
-        };
-
-        return Component::from_parts(engine, code, artifacts);
-
-        fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
-            let mut code = CodeMemory::new(mmap)?;
-            code.publish()?;
-            Ok(Arc::new(code))
-        }
+        crate::CodeBuilder::new(engine)
+            .wasm(binary, None)?
+            .wat(false)?
+            .compile_component()
     }
 
     /// Same as [`Module::deserialize`], but for components.
@@ -425,7 +363,7 @@ impl Component {
     ///
     /// If the `artifacts` are specified as `None` here then they will be
     /// deserialized from `code_memory`.
-    fn from_parts(
+    pub(crate) fn from_parts(
         engine: &Engine,
         code_memory: Arc<CodeMemory>,
         artifacts: Option<ComponentArtifacts>,
diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs
index 3cc8aae5b..1e6c2ed38 100644
--- a/crates/wasmtime/src/runtime/module.rs
+++ b/crates/wasmtime/src/runtime/module.rs
@@ -7,9 +7,8 @@ use crate::{
     types::{ExportType, ExternType, ImportType},
     Engine,
 };
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
 use once_cell::sync::OnceCell;
-use std::fs;
 use std::mem;
 use std::ops::Range;
 use std::path::Path;
@@ -239,10 +238,9 @@ impl Module {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
-        let bytes = bytes.as_ref();
-        #[cfg(feature = "wat")]
-        let bytes = wat::parse_bytes(bytes)?;
-        Self::from_binary(engine, &bytes)
+        crate::CodeBuilder::new(engine)
+            .wasm(bytes.as_ref(), None)?
+            .compile_module()
     }
 
     /// Creates a new WebAssembly `Module` from the contents of the given
@@ -276,25 +274,9 @@ impl Module {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
-        let file = file.as_ref();
-        match Self::new(
-            engine,
-            &fs::read(file)
-                .with_context(|| format!("failed to read input file: {}", file.display()))?,
-        ) {
-            Ok(m) => Ok(m),
-            Err(e) => {
-                cfg_if::cfg_if! {
-                    if #[cfg(feature = "wat")] {
-                        let mut e = e.downcast::<wat::Error>()?;
-                        e.set_path(file);
-                        bail!(e)
-                    } else {
-                        Err(e)
-                    }
-                }
-            }
-        }
+        crate::CodeBuilder::new(engine)
+            .wasm_file(file.as_ref())?
+            .compile_module()
     }
 
     /// Creates a new WebAssembly `Module` from the given in-memory `binary`
@@ -331,54 +313,10 @@ impl Module {
     #[cfg(any(feature = "cranelift", feature = "winch"))]
     #[cfg_attr(docsrs, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
     pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
-        use crate::{compile::build_artifacts, instantiate::MmapVecWrapper};
-
-        engine
-            .check_compatible_with_native_host()
-            .context("compilation settings are not compatible with the native host")?;
-
-        cfg_if::cfg_if! {
-            if #[cfg(feature = "cache")] {
-                let state = (HashedEngineCompileEnv(engine), binary);
-                let (code, info_and_types) = wasmtime_cache::ModuleCacheEntry::new(
-                    "wasmtime",
-                    engine.cache_config(),
-                )
-                .get_data_raw(
-                    &state,
-
-                    // Cache miss, compute the actual artifacts
-                    |(engine, wasm)| -> Result<_> {
-                        let (mmap, info) = build_artifacts::<MmapVecWrapper>(engine.0, wasm)?;
-                        let code = publish_mmap(mmap.0)?;
-                        Ok((code, info))
-                    },
-
-                    // Implementation of how to serialize artifacts
-                    |(_engine, _wasm), (code, _info_and_types)| {
-                        Some(code.mmap().to_vec())
-                    },
-
-                    // Cache hit, deserialize the provided artifacts
-                    |(engine, _wasm), serialized_bytes| {
-                        let code = engine.0.load_code_bytes(&serialized_bytes, ObjectKind::Module).ok()?;
-                        Some((code, None))
-                    },
-                )?;
-            } else {
-                let (mmap, info_and_types) = build_artifacts::<MmapVecWrapper>(engine, binary)?;
-                let code = publish_mmap(mmap.0)?;
-            }
-        };
-
-        let info_and_types = info_and_types.map(|(info, types)| (info, types.into()));
-        return Self::from_parts(engine, code, info_and_types);
-
-        fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
-            let mut code = CodeMemory::new(mmap)?;
-            code.publish()?;
-            Ok(Arc::new(code))
-        }
+        crate::CodeBuilder::new(engine)
+            .wasm(binary, None)?
+            .wat(false)?
+            .compile_module()
     }
 
     /// Creates a new WebAssembly `Module` from the contents of the given `file`
@@ -411,7 +349,9 @@ impl Module {
             return Module::from_parts(engine, code, None);
         }
 
-        Module::new(engine, &*mmap)
+        crate::CodeBuilder::new(engine)
+            .wasm(&mmap, Some(file.as_ref()))?
+            .compile_module()
     }
 
     /// Deserializes an in-memory compiled module previously created with
@@ -496,7 +436,7 @@ impl Module {
     /// The `info_and_types` argument is `None` when a module is being
     /// deserialized from a precompiled artifact or it's `Some` if it was just
     /// compiled and the values are already available.
-    fn from_parts(
+    pub(crate) fn from_parts(
         engine: &Engine,
         code_memory: Arc<CodeMemory>,
         info_and_types: Option<(CompiledModuleInfo, ModuleTypes)>,
@@ -1117,35 +1057,6 @@ fn _assert_send_sync() {
     _assert::<Module>();
 }
 
-/// This is a helper struct used when caching to hash the state of an `Engine`
-/// used for module compilation.
-///
-/// The hash computed for this structure is used to key the global wasmtime
-/// cache and dictates whether artifacts are reused. Consequently the contents
-/// of this hash dictate when artifacts are or aren't re-used.
-#[cfg(any(feature = "cranelift", feature = "winch"))]
-pub(crate) struct HashedEngineCompileEnv<'a>(pub &'a Engine);
-
-#[cfg(any(feature = "cranelift", feature = "winch"))]
-impl std::hash::Hash for HashedEngineCompileEnv<'_> {
-    fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
-        // Hash the compiler's state based on its target and configuration.
-        let compiler = self.0.compiler();
-        compiler.triple().hash(hasher);
-        compiler.flags().hash(hasher);
-        compiler.isa_flags().hash(hasher);
-
-        // Hash configuration state read for compilation
-        let config = self.0.config();
-        self.0.tunables().hash(hasher);
-        config.features.hash(hasher);
-        config.wmemcheck.hash(hasher);
-
-        // Catch accidental bugs of reusing across crate versions.
-        config.module_version.hash(hasher);
-    }
-}
-
 impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
     fn module(&self) -> &Arc<wasmtime_environ::Module> {
         self.module.module()
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 1413874ee..2c1eed303 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -207,15 +207,26 @@ impl RunCommand {
             }
         }
 
+        // Pre-emptively initialize and install a Tokio runtime ambiently in the
+        // environment when executing the module. Without this whenever a WASI
+        // call is made that needs to block on a future a Tokio runtime is
+        // configured and entered, and this appears to be slower than simply
+        // picking an existing runtime out of the environment and using that.
+        // The goal of this is to improve the performance of WASI-related
+        // operations that block in the CLI since the CLI doesn't use async to
+        // invoke WebAssembly.
+        let result = wasmtime_wasi::runtime::with_ambient_tokio_runtime(|| {
+            self.load_main_module(&mut store, &mut linker, &main, modules)
+                .with_context(|| {
+                    format!(
+                        "failed to run main module `{}`",
+                        self.module_and_args[0].to_string_lossy()
+                    )
+                })
+        });
+
         // Load the main wasm module.
-        match self
-            .load_main_module(&mut store, &mut linker, &main, modules)
-            .with_context(|| {
-                format!(
-                    "failed to run main module `{}`",
-                    self.module_and_args[0].to_string_lossy()
-                )
-            }) {
+        match result {
             Ok(()) => (),
             Err(e) => {
                 // Exit the process if Wasmtime understands the error;
@@ -799,6 +810,12 @@ impl RunCommand {
         let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
         builder.inherit_stdio().args(&self.compute_argv()?);
 
+        // It's ok to block the current thread since we're the only thread in
+        // the program as the CLI. This helps improve the performance of some
+        // blocking operations in WASI, for example, by skipping the
+        // back-and-forth between sync and async.
+        builder.allow_blocking_current_thread(true);
+
         if self.run.common.wasi.inherit_env == Some(true) {
             for (k, v) in std::env::vars() {
                 builder.env(&k, &v);
diff --git a/src/common.rs b/src/common.rs
index 7fa842f4e..3257fca92 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -195,19 +195,24 @@ impl RunCommon {
                     e.set_path(path);
                     e
                 })?;
-                let _ = path;
                 if wasmparser::Parser::is_component(&bytes) {
                     #[cfg(feature = "component-model")]
                     {
                         self.ensure_allow_components()?;
-                        RunTarget::Component(Component::new(engine, &bytes)?)
+                        let component = wasmtime::CodeBuilder::new(engine)
+                            .wasm(&bytes, Some(path))?
+                            .compile_component()?;
+                        RunTarget::Component(component)
                     }
                     #[cfg(not(feature = "component-model"))]
                     {
                         bail!("support for components was not enabled at compile time");
                     }
                 } else {
-                    RunTarget::Core(Module::new(engine, &bytes)?)
+                    let module = wasmtime::CodeBuilder::new(engine)
+                        .wasm(&bytes, Some(path))?
+                        .compile_module()?;
+                    RunTarget::Core(module)
                 }
             }
             #[cfg(not(any(feature = "cranelift", feature = "winch")))]
diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs
index 978d16561..53e82fa46 100644
--- a/winch/codegen/src/codegen/env.rs
+++ b/winch/codegen/src/codegen/env.rs
@@ -328,8 +328,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
                     current_length_offset,
                     style,
                     ty: if plan.memory.memory64 {
-                        // TODO: Add support for 64-bit memories.
-                        unimplemented!("memory64")
+                        WasmValType::I64
                     } else {
                         WasmValType::I32
                     },
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 3ad1ec585..da9051302 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -10,6 +10,7 @@ use crate::masm::{
     DivKind, ExtendKind, FloatCmpKind, IntCmpKind, MacroAssembler, MemMoveDirection, OperandSize,
     RegImm, RemKind, RoundingMode, SPOffset, ShiftKind, TruncKind,
 };
+use crate::reg::Reg;
 use crate::stack::{TypedReg, Val};
 use cranelift_codegen::ir::TrapCode;
 use regalloc2::RegClass;
@@ -1593,8 +1594,24 @@ where
         //   [ vmctx, delta, index ]
         self.context.stack.extend([mem.try_into().unwrap()]);
 
+        let heap = self.env.resolve_heap(MemoryIndex::from_u32(mem));
         let builtin = self.env.builtins.memory32_grow::<M::ABI, M::Ptr>();
-        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin));
+
+        // The memory32_grow builtin returns a pointer type, therefore we must
+        // ensure that the return type is representative of the address space of
+        // the heap type.
+        match (self.env.ptr_type(), heap.ty) {
+            (WasmValType::I64, WasmValType::I64) => {}
+            // When the heap type is smaller than the pointer type, we adjust
+            // the result of the memory32_grow builtin.
+            (WasmValType::I64, WasmValType::I32) => {
+                let top: Reg = self.context.pop_to_reg(self.masm, None).into();
+                self.masm.wrap(top.into(), top.into());
+                self.context.stack.push(TypedReg::i32(top).into());
+            }
+            _ => unimplemented!("Support for 32-bit platforms"),
+        }
     }
 
     fn visit_data_drop(&mut self, data_index: u32) {


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/spectre.clif b/cranelift/filetests/filetests/egraph/spectre.clif
new file mode 100644
index 000000000..ca3432f61
--- /dev/null
+++ b/cranelift/filetests/filetests/egraph/spectre.clif
@@ -0,0 +1,26 @@
+test optimize
+set opt_level=speed
+target x86_64
+
+function %same_value(i8, i64) -> i64 {
+block0(v0: i8, v1: i64):
+    v2 = select_spectre_guard v0, v1, v1
+    return v2
+}
+; check: return v1
+
+function %const_true(i64, i64) -> i64 {
+block0(v0: i64, v1: i64):
+    v2 = iconst.i8 42
+    v3 = select_spectre_guard v2, v0, v1
+    return v3
+}
+; check: return v0
+
+function %const_false(i64, i64) -> i64 {
+block0(v0: i64, v1: i64):
+    v2 = iconst.i8 0
+    v3 = select_spectre_guard v2, v0, v1
+    return v3
+}
+; check: return v1
diff --git a/tests/disas/pcc-imported-memory.wat b/tests/disas/pcc-imported-memory.wat
new file mode 100644
index 000000000..5bd75ad01
--- /dev/null
+++ b/tests/disas/pcc-imported-memory.wat
@@ -0,0 +1,78 @@
+;;! target = "x86_64"
+;;! test = "compile"
+;;! flags = [ "-Oopt-level=2", "-Cpcc=y" ]
+
+(module
+  (type (;0;) (func))
+  (import "" "" (memory (;0;) 1))
+  (func (;0;) (type 0)
+    (local i32 i32)
+    memory.size
+    local.set 0
+    block  ;; label = @1
+      block  ;; label = @2
+        memory.size
+        i32.const 65536
+        i32.mul
+        i32.const 4
+        local.get 0
+        i32.add
+        i32.le_u
+        br_if 0 (;@2;)
+        local.get 0
+        i32.const 0
+        i32.le_s
+        br_if 0 (;@2;)
+        local.get 0
+        i32.load align=1
+        local.set 1
+        br 1 (;@1;)
+      end
+      i32.const 0
+      local.set 1
+    end
+    local.get 1
+    drop))
+
+;; function u0:0:
+;;   pushq   %rbp
+;;   unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
+;;   movq    %rsp, %rbp
+;;   movq    8(%rdi), %r10
+;;   movq    0(%r10), %r10
+;;   cmpq    %rsp, %r10
+;;   jnbe #trap=stk_ovf
+;;   unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
+;; block0:
+;;   movq    72(%rdi), %r10
+;;   movq    8(%r10), %rax
+;;   movl    $65536, %r10d
+;;   xorq    %rdx, %rdx, %rdx
+;;   div     %rax, %rdx, %r10, %rax, %rdx ; trap=int_divz
+;;   movq    %rax, %r9
+;;   shll    $16, %r9d, %r9d
+;;   lea     4(%rax), %r10d
+;;   cmpl    %r10d, %r9d
+;;   jbe     label1; j label2
+;; block1:
+;;   jmp     label5
+;; block2:
+;;   testl   %eax, %eax
+;;   jle     label3; j label4
+;; block3:
+;;   jmp     label5
+;; block4:
+;;   movq    72(%rdi), %rcx
+;;   movq    0(%rcx), %rcx
+;;   movl    %eax, %edx
+;;   movl    0(%rcx,%rdx,1), %edi
+;;   jmp     label6
+;; block5:
+;;   xorl    %edi, %edi, %edi
+;;   jmp     label6
+;; block6:
+;;   jmp     label7
+;; block7:
+;;   movq    %rbp, %rsp
+;;   popq    %rbp
+;;   ret
diff --git a/tests/disas/typed-funcrefs.wat b/tests/disas/typed-funcrefs.wat
index 10ba6a955..8bf6beb37 100644
--- a/tests/disas/typed-funcrefs.wat
+++ b/tests/disas/typed-funcrefs.wat
@@ -151,12 +151,9 @@
 ;;                                     v32 -> v4
 ;;                                     v33 -> v5
 ;; @0048                               v12 = load.i64 notrap aligned v0+72
-;;                                     v62 = iconst.i8 0
-;; @0048                               v15 = iconst.i64 0
 ;;                                     v70 = iconst.i64 8
 ;; @0048                               v14 = iadd v12, v70  ; v70 = 8
-;; @0048                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
-;; @0048                               v17 = load.i64 table_oob aligned table v16
+;; @0048                               v17 = load.i64 table_oob aligned table v14
 ;;                                     v58 = iconst.i64 -2
 ;; @0048                               v18 = band v17, v58  ; v58 = -2
 ;; @0048                               brif v17, block3(v18), block2
@@ -175,22 +172,19 @@
 ;; @004a                               v26 = load.i64 notrap aligned readonly v19+32
 ;; @004a                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
 ;; @005b                               v38 = load.i64 notrap aligned v0+72
-;;                                     v79 = iconst.i8 0
-;;                                     v80 = iconst.i64 0
 ;;                                     v78 = iconst.i64 16
 ;; @005b                               v40 = iadd v38, v78  ; v78 = 16
-;; @005b                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
-;; @005b                               v43 = load.i64 table_oob aligned table v42
-;;                                     v81 = iconst.i64 -2
-;;                                     v82 = band v43, v81  ; v81 = -2
-;; @005b                               brif v43, block5(v82), block4
+;; @005b                               v43 = load.i64 table_oob aligned table v40
+;;                                     v79 = iconst.i64 -2
+;;                                     v80 = band v43, v79  ; v79 = -2
+;; @005b                               brif v43, block5(v80), block4
 ;;
 ;;                                 block4 cold:
-;;                                     v83 = load.i64 notrap aligned readonly v0+56
-;;                                     v84 = load.i64 notrap aligned readonly v83+72
-;;                                     v85 = iconst.i32 0
+;;                                     v81 = load.i64 notrap aligned readonly v0+56
+;;                                     v82 = load.i64 notrap aligned readonly v81+72
+;;                                     v83 = iconst.i32 0
 ;; @0059                               v34 = iconst.i32 2
-;; @005b                               v50 = call_indirect sig0, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @005b                               v50 = call_indirect sig0, v82(v0, v83, v34)  ; v83 = 0, v34 = 2
 ;; @005b                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):
@@ -227,12 +221,9 @@
 ;;                                     v32 -> v4
 ;;                                     v33 -> v5
 ;; @0075                               v12 = load.i64 notrap aligned v0+72
-;;                                     v62 = iconst.i8 0
-;; @0075                               v15 = iconst.i64 0
 ;;                                     v70 = iconst.i64 8
 ;; @0075                               v14 = iadd v12, v70  ; v70 = 8
-;; @0075                               v16 = select_spectre_guard v62, v15, v14  ; v62 = 0, v15 = 0
-;; @0075                               v17 = load.i64 table_oob aligned table v16
+;; @0075                               v17 = load.i64 table_oob aligned table v14
 ;;                                     v58 = iconst.i64 -2
 ;; @0075                               v18 = band v17, v58  ; v58 = -2
 ;; @0075                               brif v17, block3(v18), block2
@@ -251,22 +242,19 @@
 ;; @0075                               v26 = load.i64 notrap aligned readonly v19+32
 ;; @0075                               v27 = call_indirect sig0, v25(v26, v0, v2, v3, v4, v5)
 ;; @0087                               v38 = load.i64 notrap aligned v0+72
-;;                                     v79 = iconst.i8 0
-;;                                     v80 = iconst.i64 0
 ;;                                     v78 = iconst.i64 16
 ;; @0087                               v40 = iadd v38, v78  ; v78 = 16
-;; @0087                               v42 = select_spectre_guard v79, v80, v40  ; v79 = 0, v80 = 0
-;; @0087                               v43 = load.i64 table_oob aligned table v42
-;;                                     v81 = iconst.i64 -2
-;;                                     v82 = band v43, v81  ; v81 = -2
-;; @0087                               brif v43, block5(v82), block4
+;; @0087                               v43 = load.i64 table_oob aligned table v40
+;;                                     v79 = iconst.i64 -2
+;;                                     v80 = band v43, v79  ; v79 = -2
+;; @0087                               brif v43, block5(v80), block4
 ;;
 ;;                                 block4 cold:
-;;                                     v83 = load.i64 notrap aligned readonly v0+56
-;;                                     v84 = load.i64 notrap aligned readonly v83+72
-;;                                     v85 = iconst.i32 0
+;;                                     v81 = load.i64 notrap aligned readonly v0+56
+;;                                     v82 = load.i64 notrap aligned readonly v81+72
+;;                                     v83 = iconst.i32 0
 ;; @0085                               v34 = iconst.i32 2
-;; @0087                               v50 = call_indirect sig1, v84(v0, v85, v34)  ; v85 = 0, v34 = 2
+;; @0087                               v50 = call_indirect sig1, v82(v0, v83, v34)  ; v83 = 0, v34 = 2
 ;; @0087                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):
diff --git a/tests/misc_testsuite/simd/canonicalize-nan.wast b/tests/misc_testsuite/simd/canonicalize-nan.wast
index b35a941f0..8a0ae71d9 100644
--- a/tests/misc_testsuite/simd/canonicalize-nan.wast
+++ b/tests/misc_testsuite/simd/canonicalize-nan.wast
@@ -39,6 +39,32 @@
     f64.reinterpret_i64
     f32.demote_f64
     i32.reinterpret_f32)
+
+  (func (export "reinterpret-and-promote") (param i32) (result i64)
+    local.get 0
+    f32.reinterpret_i32
+    f64.promote_f32
+    i64.reinterpret_f64)
+
+  (func (export "copysign-and-demote") (param f64) (result f32)
+    local.get 0
+    f64.const -0x1
+    f64.copysign
+    f32.demote_f64)
+
+  (func (export "copysign-and-promote") (param f32) (result f64)
+    local.get 0
+    f32.const -0x1
+    f32.copysign
+    f64.promote_f32)
+
+  (func (export "f32x4.demote_f64x2_zero") (param v128) (result v128)
+    local.get 0
+    f32x4.demote_f64x2_zero)
+
+  (func (export "f64x2.promote_low_f32x4") (param v128) (result v128)
+    local.get 0
+    f64x2.promote_low_f32x4)
 )
 
 (assert_return (invoke "f32x4.floor" (v128.const f32x4 1 -2.2 3.4 nan))
@@ -65,3 +91,17 @@
 
 (assert_return (invoke "reinterpret-and-demote" (i64.const 0xfffefdfccccdcecf))
                (i32.const 0x7fc00000))
+(assert_return (invoke "reinterpret-and-promote" (i32.const 0xfffefdfc))
+               (i64.const 0x7ff8000000000000))
+(assert_return (invoke "copysign-and-demote" (f64.const nan))
+               (f32.const nan:0x7fc00000))
+(assert_return (invoke "copysign-and-promote" (f32.const nan))
+               (f64.const nan:0x7ff8000000000000))
+
+(assert_return (invoke "f32x4.demote_f64x2_zero"
+               (v128.const i64x2 0xfffefdfccccdcecf 0xfffefdfccccdcecf))
+               (v128.const f32x4 nan:0x7fc00000 nan:0x7fc00000 0 0))
+
+(assert_return (invoke "f64x2.promote_low_f32x4"
+               (v128.const i32x4 0xfffefdfc 0xfffefdfc 0 0))
+               (v128.const f64x2 nan:0x7ff8000000000000 nan:0x7ff8000000000000))
diff --git a/winch/filetests/filetests/x64/load/grow_load.wat b/winch/filetests/filetests/x64/load/grow_load.wat
new file mode 100644
index 000000000..1e1bf5e51
--- /dev/null
+++ b/winch/filetests/filetests/x64/load/grow_load.wat
@@ -0,0 +1,103 @@
+;;! target = "x86_64"
+(module
+  (type (;0;) (func (param f32 f64 i64 i64 f64 f64 f32) (result f32 f64 f32)))
+  (func (;0;) (type 0) (param f32 f64 i64 i64 f64 f64 f32) (result f32 f64 f32)
+    global.get 1
+    i32.eqz
+    if ;; label = @1
+      unreachable
+    end
+    global.get 1
+    i32.const 1
+    i32.sub
+    global.set 1
+    memory.size
+    memory.grow
+    i64.load8_s offset=143396
+    (drop)
+    (f32.const 0)
+    (f64.const 0)
+    (f32.const 0)
+  )
+  (memory (;1;) 10 10)
+  (global (;0;) f32 f32.const 0x1.d6a0d6p+87 (;=284477330000000000000000000;))
+  (global (;1;) (mut i32) i32.const 1000)
+  (export "main" (func 0))
+  (export "0" (memory 0))
+  (export "1" (global 0))
+)
+;;      	 55                   	push	rbp
+;;      	 4889e5               	mov	rbp, rsp
+;;      	 4c8b5f08             	mov	r11, qword ptr [rdi + 8]
+;;      	 4d8b1b               	mov	r11, qword ptr [r11]
+;;      	 4981c360000000       	add	r11, 0x60
+;;      	 4939e3               	cmp	r11, rsp
+;;      	 0f87f1000000         	ja	0x10c
+;;   1b:	 4989fe               	mov	r14, rdi
+;;      	 4883ec50             	sub	rsp, 0x50
+;;      	 48897c2448           	mov	qword ptr [rsp + 0x48], rdi
+;;      	 4889742440           	mov	qword ptr [rsp + 0x40], rsi
+;;      	 f30f1144243c         	movss	dword ptr [rsp + 0x3c], xmm0
+;;      	 f20f114c2430         	movsd	qword ptr [rsp + 0x30], xmm1
+;;      	 4889542428           	mov	qword ptr [rsp + 0x28], rdx
+;;      	 48894c2420           	mov	qword ptr [rsp + 0x20], rcx
+;;      	 f20f11542418         	movsd	qword ptr [rsp + 0x18], xmm2
+;;      	 f20f115c2410         	movsd	qword ptr [rsp + 0x10], xmm3
+;;      	 f30f1164240c         	movss	dword ptr [rsp + 0xc], xmm4
+;;      	 4c890424             	mov	qword ptr [rsp], r8
+;;      	 418b4670             	mov	eax, dword ptr [r14 + 0x70]
+;;      	 83f800               	cmp	eax, 0
+;;      	 b800000000           	mov	eax, 0
+;;      	 400f94c0             	sete	al
+;;      	 85c0                 	test	eax, eax
+;;      	 0f8402000000         	je	0x72
+;;   70:	 0f0b                 	ud2	
+;;      	 418b4670             	mov	eax, dword ptr [r14 + 0x70]
+;;      	 83e801               	sub	eax, 1
+;;      	 41894670             	mov	dword ptr [r14 + 0x70], eax
+;;      	 498b4658             	mov	rax, qword ptr [r14 + 0x58]
+;;      	 c1e810               	shr	eax, 0x10
+;;      	 4883ec04             	sub	rsp, 4
+;;      	 890424               	mov	dword ptr [rsp], eax
+;;      	 4d8b5e38             	mov	r11, qword ptr [r14 + 0x38]
+;;      	 498b0b               	mov	rcx, qword ptr [r11]
+;;      	 4883ec0c             	sub	rsp, 0xc
+;;      	 4c89f7               	mov	rdi, r14
+;;      	 8b74240c             	mov	esi, dword ptr [rsp + 0xc]
+;;      	 ba00000000           	mov	edx, 0
+;;      	 ffd1                 	call	rcx
+;;      	 4883c40c             	add	rsp, 0xc
+;;      	 4883c404             	add	rsp, 4
+;;      	 4c8b742448           	mov	r14, qword ptr [rsp + 0x48]
+;;      	 89c0                 	mov	eax, eax
+;;      	 498b4e50             	mov	rcx, qword ptr [r14 + 0x50]
+;;      	 4801c1               	add	rcx, rax
+;;      	 4881c124300200       	add	rcx, 0x23024
+;;      	 480fbe01             	movsx	rax, byte ptr [rcx]
+;;      	 f30f100543000000     	movss	xmm0, dword ptr [rip + 0x43]
+;;      	 4883ec0c             	sub	rsp, 0xc
+;;      	 f2440f103d3e000000   	
+;; 				movsd	xmm15, qword ptr [rip + 0x3e]
+;;      	 f2440f113c24         	movsd	qword ptr [rsp], xmm15
+;;      	 f3440f103d27000000   	
+;; 				movss	xmm15, dword ptr [rip + 0x27]
+;;      	 f3440f117c2408       	movss	dword ptr [rsp + 8], xmm15
+;;      	 488b44240c           	mov	rax, qword ptr [rsp + 0xc]
+;;      	 415b                 	pop	r11
+;;      	 4c8918               	mov	qword ptr [rax], r11
+;;      	 448b1c24             	mov	r11d, dword ptr [rsp]
+;;      	 4883c404             	add	rsp, 4
+;;      	 44895808             	mov	dword ptr [rax + 8], r11d
+;;      	 4883c450             	add	rsp, 0x50
+;;      	 5d                   	pop	rbp
+;;      	 c3                   	ret	
+;;  10c:	 0f0b                 	ud2	
+;;  10e:	 0000                 	add	byte ptr [rax], al
+;;  110:	 0000                 	add	byte ptr [rax], al
+;;  112:	 0000                 	add	byte ptr [rax], al
+;;  114:	 0000                 	add	byte ptr [rax], al
+;;  116:	 0000                 	add	byte ptr [rax], al
+;;  118:	 0000                 	add	byte ptr [rax], al
+;;  11a:	 0000                 	add	byte ptr [rax], al
+;;  11c:	 0000                 	add	byte ptr [rax], al
+;;  11e:	 0000                 	add	byte ptr [rax], al