Repository: bytecodealliance/wasmtime
Pull request #5595 (2023-01-19 06:54:12)

fix issue #5497 #5524 #5526.
fix issue #5497 #5524 #5526. 

I make a mistaken about reg alloc.

<img width="436" alt="Screenshot 2023-01-19 at 15 40 43" src="https://user-images.githubusercontent.com/96557710/213383238-e22545d8-c6e1-424f-9280-654bbd7c924a.png">

instruction like `Select` in `MInst` can lead to two move instruction. 
We can somehow enter this situation.
~~~
move t1,t2
move t2,t1
~~~
this is wrong.


yuyang-ok: by the way.
how to get disassembly easy.
@afonso360 

like blow.

~~~
Disassembly of 28 bytes:
   0:   63 46 c6 00             blt     a2, a2, 0xc
   4:   63 18 c6 00             bne     a2, a2, 0x10
   8:   63 f6 b5 00             bgeu    a1, a1, 0xc
   c:   13 e5 05 00             ori     a0, a1, 0
  10:   6f 00 80 00             j       8
  14:   13 e5 05 00             ori     a0, a1, 0
  18:   67 80 00 00             ret
~~~




afonso360: 👋 Hey! I use `clif-util compile` to get those disassembled examples. So, something along the lines of `cargo run -- compile -D --target riscv64 ./testcase.clif` in the cranelift directory.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index ffa6d57d3..fc6bda086 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -766,9 +766,12 @@ dependencies = [
  "log",
  "pretty_env_logger",
  "rayon",
+ "serde",
+ "similar",
  "target-lexicon",
  "termcolor",
  "thiserror",
+ "toml",
  "walkdir",
  "wat",
 ]
@@ -1588,9 +1591,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
 
 [[package]]
 name = "ittapi"
-version = "0.3.1"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "663fe0550070071ff59e981864a9cd3ee1c869ed0a088140d9ac4dc05ea6b1a1"
+checksum = "2e648c437172ce7d3ac35ca11a068755072054826fa455a916b43524fa4a62a7"
 dependencies = [
  "anyhow",
  "ittapi-sys",
@@ -1599,9 +1602,9 @@ dependencies = [
 
 [[package]]
 name = "ittapi-sys"
-version = "0.3.1"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e21911b7183f38c71d75ab478a527f314e28db51027037ece2e5511ed9410703"
+checksum = "a9b32a4d23f72548178dde54f3c12c6b6a08598e25575c0d0fa5bd861e0dc1a5"
 dependencies = [
  "cc",
 ]
@@ -2897,16 +2900,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.18.1"
+version = "1.18.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"
+checksum = "8bfb875c82dc0a4f1f37a30e720dee181a2b3a06a428b0fc6873ea38d6407850"
 dependencies = [
  "bytes",
  "libc",
  "memchr",
  "mio",
  "num_cpus",
- "once_cell",
  "pin-project-lite",
  "socket2",
  "tokio-macros",
@@ -3949,6 +3951,33 @@ dependencies = [
  "wasmparser",
 ]
 
+[[package]]
+name = "winch-filetests"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "capstone",
+ "cranelift-codegen",
+ "serde",
+ "similar",
+ "target-lexicon",
+ "toml",
+ "wasmtime-environ",
+ "wat",
+ "winch-codegen",
+ "winch-test-macros",
+]
+
+[[package]]
+name = "winch-test-macros"
+version = "0.0.0"
+dependencies = [
+ "glob",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "winch-tools"
 version = "0.0.0"
@@ -3957,11 +3986,17 @@ dependencies = [
  "capstone",
  "clap 3.2.8",
  "cranelift-codegen",
+ "glob",
+ "serde",
+ "similar",
  "target-lexicon",
+ "toml",
  "wasmparser",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
+ "winch-filetests",
+ "winch-test-macros",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index f7feff55d..e72cddff1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -156,6 +156,8 @@ cranelift-bforest = { path = "cranelift/bforest", version = "0.93.0" }
 cranelift = { path = "cranelift/umbrella", version = "0.93.0" }
 
 winch-codegen = { path = "winch/codegen", version = "=0.4.0" }
+winch-filetests = { path = "winch/filetests" }
+winch-test-macros = { path = "winch/test-macros" }
 
 target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] }
 anyhow = "1.0.22"
@@ -186,6 +188,10 @@ bitflags = "1.2"
 thiserror = "1.0.15"
 async-trait = "0.1.42"
 heck = "0.4"
+similar = "2.1.0"
+toml = "0.5.9"
+serde = "1.0.94"
+glob = "0.3.0"
 
 [features]
 default = [
diff --git a/README.md b/README.md
index 3f7c30213..d59c36806 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,9 @@ Hello, world!
 ## Language Support
 
 You can use Wasmtime from a variety of different languages through embeddings of
-the implementation:
+the implementation.
+
+Languages supported by the Bytecode Alliance:
 
 * **[Rust]** - the [`wasmtime` crate]
 * **[C]** - the [`wasm.h`, `wasi.h`, and `wasmtime.h` headers][c-headers], [CMake](crates/c-api/CMakeLists.txt) or [`wasmtime` Conan package]
@@ -118,6 +120,10 @@ the implementation:
 * **[Go]** - the [`wasmtime-go` repository]
 * **[Ruby]** - the [`wasmtime` gem]
 
+Languages supported by the community:
+
+* **[Elixir]** - the [`wasmex` hex package]
+
 [Rust]: https://bytecodealliance.github.io/wasmtime/lang-rust.html
 [C]: https://bytecodealliance.github.io/wasmtime/examples-c-embed.html
 [`wasmtime` crate]: https://crates.io/crates/wasmtime
@@ -133,6 +139,8 @@ the implementation:
 [`wasmtime-cpp` Conan package]: https://conan.io/center/wasmtime-cpp
 [Ruby]: https://bytecodealliance.github.io/wasmtime/lang-ruby.html
 [`wasmtime` gem]: https://rubygems.org/gems/wasmtime
+[Elixir]: https://docs.wasmtime.dev/lang-elixir.html
+[`wasmex` hex package]: https://hex.pm/packages/wasmex
 
 ## Documentation
 
diff --git a/RELEASES.md b/RELEASES.md
index 30f6cd11a..8e7f581f4 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -12,7 +12,7 @@ Unreleased.
 
 ## 5.0.0
 
-Unreleased.
+Released 2023-01-20
 
 ### Added
 
diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml
index a9f780946..a34d6a639 100644
--- a/cranelift/Cargo.toml
+++ b/cranelift/Cargo.toml
@@ -46,6 +46,9 @@ thiserror = { workspace = true }
 walkdir = "2.2"
 anyhow = { workspace = true }
 clap = { workspace = true }
+similar = { workspace = true }
+toml = { workspace = true }
+serde = { workspace = true }
 
 [features]
 default = ["disas", "wasm", "cranelift-codegen/all-arch", "cranelift-codegen/trace-log", "souper-harvest"]
diff --git a/cranelift/codegen/meta/src/shared/settings.rs b/cranelift/codegen/meta/src/shared/settings.rs
index e51cd5a5a..2db1f1303 100644
--- a/cranelift/codegen/meta/src/shared/settings.rs
+++ b/cranelift/codegen/meta/src/shared/settings.rs
@@ -58,10 +58,12 @@ pub(crate) fn define() -> SettingGroup {
         "Enable egraph-based optimization.",
         r#"
             This enables an optimization phase that converts CLIF to an egraph (equivalence graph)
-            representation, performs various rewrites, and then converts it back. This can result in
-            better optimization, but is currently considered experimental.
+            representation, performs various rewrites, and then converts it back. This should result in
+            better optimization, but the traditional optimization pass structure is also still
+            available by setting this to `false`. The `false` setting will eventually be
+            deprecated and removed.
         "#,
-        false,
+        true,
     );
 
     settings.add_bool(
diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs
index 175f8e8ea..4da6cbe04 100644
--- a/cranelift/codegen/src/context.rs
+++ b/cranelift/codegen/src/context.rs
@@ -185,18 +185,20 @@ impl Context {
         self.compute_domtree();
         self.eliminate_unreachable_code(isa)?;
 
-        if isa.flags().use_egraphs() || opt_level != OptLevel::None {
+        if opt_level != OptLevel::None {
             self.dce(isa)?;
         }
 
         self.remove_constant_phis(isa)?;
 
-        if isa.flags().use_egraphs() {
-            self.egraph_pass()?;
-        } else if opt_level != OptLevel::None && isa.flags().enable_alias_analysis() {
-            for _ in 0..2 {
-                self.replace_redundant_loads()?;
-                self.simple_gvn(isa)?;
+        if opt_level != OptLevel::None {
+            if isa.flags().use_egraphs() {
+                self.egraph_pass()?;
+            } else if isa.flags().enable_alias_analysis() {
+                for _ in 0..2 {
+                    self.replace_redundant_loads()?;
+                    self.simple_gvn(isa)?;
+                }
             }
         }
 
diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs
index 6f8bdbb73..02bfeb013 100644
--- a/cranelift/codegen/src/egraph.rs
+++ b/cranelift/codegen/src/egraph.rs
@@ -7,7 +7,7 @@ use crate::dominator_tree::DominatorTree;
 use crate::egraph::domtree::DomTreeWithChildren;
 use crate::egraph::elaborate::Elaborator;
 use crate::fx::FxHashSet;
-use crate::inst_predicates::is_pure_for_egraph;
+use crate::inst_predicates::{is_mergeable_for_egraph, is_pure_for_egraph};
 use crate::ir::{
     DataFlowGraph, Function, Inst, InstructionData, Type, Value, ValueDef, ValueListPool,
 };
@@ -284,17 +284,59 @@ where
     /// the layout.
     fn optimize_skeleton_inst(&mut self, inst: Inst) -> bool {
         self.stats.skeleton_inst += 1;
-        // Not pure, but may still be a load or store:
-        // process it to see if we can optimize it.
-        if let Some(new_result) =
+
+        // First, can we try to deduplicate? We need to keep some copy
+        // of the instruction around because it's side-effecting, but
+        // we may be able to reuse an earlier instance of it.
+        if is_mergeable_for_egraph(self.func, inst) {
+            let result = self.func.dfg.inst_results(inst)[0];
+            trace!(" -> mergeable side-effecting op {}", inst);
+            let inst = NewOrExistingInst::Existing(inst);
+            let gvn_context = GVNContext {
+                union_find: self.eclasses,
+                value_lists: &self.func.dfg.value_lists,
+            };
+
+            // Does this instruction already exist? If so, add entries to
+            // the value-map to rewrite uses of its results to the results
+            // of the original (existing) instruction. If not, optimize
+            // the new instruction.
+            let key = inst.get_inst_key(&self.func.dfg);
+            if let Some(&orig_result) = self.gvn_map.get(&key, &gvn_context) {
+                // Hit in GVN map -- reuse value.
+                self.value_to_opt_value[result] = orig_result;
+                self.eclasses.union(orig_result, result);
+                trace!(" -> merges result {} to {}", result, orig_result);
+                true
+            } else {
+                // Otherwise, insert it into the value-map.
+                self.value_to_opt_value[result] = result;
+                self.gvn_map.insert(key, result, &gvn_context);
+                trace!(" -> inserts as new (no GVN)");
+                false
+            }
+        }
+        // Otherwise, if a load or store, process it with the alias
+        // analysis to see if we can optimize it (rewrite in terms of
+        // an earlier load or stored value).
+        else if let Some(new_result) =
             self.alias_analysis
                 .process_inst(self.func, self.alias_analysis_state, inst)
         {
             self.stats.alias_analysis_removed += 1;
             let result = self.func.dfg.first_result(inst);
+            trace!(
+                " -> inst {} has result {} replaced with {}",
+                inst,
+                result,
+                new_result
+            );
             self.value_to_opt_value[result] = new_result;
             true
-        } else {
+        }
+        // Otherwise, generic side-effecting op -- always keep it, and
+        // set its results to identity-map to original values.
+        else {
             // Set all results to identity-map to themselves
             // in the value-to-opt-value map.
             for &result in self.func.dfg.inst_results(inst) {
diff --git a/cranelift/codegen/src/inst_predicates.rs b/cranelift/codegen/src/inst_predicates.rs
index 8eea3e383..3c28899bd 100644
--- a/cranelift/codegen/src/inst_predicates.rs
+++ b/cranelift/codegen/src/inst_predicates.rs
@@ -74,6 +74,25 @@ pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool {
     has_one_result && (is_readonly_load || (!op.can_load() && !trivially_has_side_effects(op)))
 }
 
+/// Can the given instruction be merged into another copy of itself?
+/// These instructions may have side-effects, but as long as we retain
+/// the first instance of the instruction, the second and further
+/// instances are redundant if they would produce the same trap or
+/// result.
+pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool {
+    let op = func.dfg.insts[inst].opcode();
+    // We can only merge one-result operators due to the way that GVN
+    // is structured in the egraph implementation.
+    let has_one_result = func.dfg.inst_results(inst).len() == 1;
+    has_one_result
+        // Loads/stores are handled by alias analysis and not
+        // otherwise mergeable.
+        && !op.can_load()
+        && !op.can_store()
+        // Can only have idempotent side-effects.
+        && (!has_side_effect(func, inst) || op.side_effects_idempotent())
+}
+
 /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load,
 /// but not the get_pinned_reg opcode?
 pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool {
diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle
index 31fdad31a..2031cf924 100644
--- a/cranelift/codegen/src/isa/riscv64/inst.isle
+++ b/cranelift/codegen/src/isa/riscv64/inst.isle
@@ -1028,7 +1028,7 @@
     ((tmp Reg (gen_extend r $true from_bits 64))
       (tmp2 Reg (alu_rrr (AluOPRRR.Slt) tmp (zero_reg)))
       (high Reg (gen_extend tmp2 $true 1 64)))
-    (value_regs (gen_move2 r $I64 $I64) high)))
+    (value_regs (gen_move2 tmp $I64 $I64) high)))
 
 
 ;;;; for I128 unsigned extend.
diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
index 36bdc2aa7..4bdb66c7c 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
@@ -1606,12 +1606,11 @@ impl MachInstEmit for Inst {
                                 val: &ValueRegs<Reg>,
                                 sink: &mut MachBuffer<Inst>,
                                 state: &mut EmitState| {
-                    let ty = if ty.bits() == 128 { I64 } else { ty };
                     let mut insts = SmallInstVec::new();
                     insts.push(Inst::Mov {
                         rd: dst[0],
                         rm: val.regs()[0],
-                        ty,
+                        ty: I64,
                     });
                     if ty.bits() == 128 {
                         insts.push(Inst::Mov {
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 64f2e5a5d..1f759a9b5 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -464,7 +464,9 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
             collector.reg_use(condition);
             collector.reg_uses(x.regs());
             collector.reg_uses(y.regs());
-            collector.reg_defs(&dst[..]);
+            for d in dst.iter() {
+                collector.reg_early_def(d.clone());
+            }
         }
         &Inst::ReferenceCheck { rd, x, .. } => {
             collector.reg_use(x);
@@ -491,7 +493,9 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
         } => {
             collector.reg_uses(x.regs());
             collector.reg_uses(y.regs());
-            collector.reg_defs(&dst[..]);
+            for d in dst.iter() {
+                collector.reg_early_def(d.clone());
+            }
         }
 
         &Inst::Csr { rd, rs, .. } => {
@@ -534,7 +538,7 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
             collector.reg_use(test);
             collector.reg_uses(x.regs());
             collector.reg_uses(y.regs());
-            rd.iter().for_each(|r| collector.reg_def(*r));
+            rd.iter().for_each(|r| collector.reg_early_def(*r));
         }
         &Inst::RawData { .. } => {}
         &Inst::AtomicStore { src, p, .. } => {
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index c66d3ac2a..9d27be301 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -275,7 +275,6 @@ impl ABIMachineSpec for S390xMachineDeps {
             } else if call_conv.extends_wasmtime() {
                 panic!("i128 args/return values not supported in the Wasmtime ABI");
             } else {
-                assert!(param.extension == ir::ArgumentExtension::None);
                 // We must pass this by implicit reference.
                 if args_or_rets == ArgsOrRets::Rets {
                     // For return values, just force them to memory.
diff --git a/cranelift/codegen/src/isa/s390x/inst.isle b/cranelift/codegen/src/isa/s390x/inst.isle
index 458dfe478..815513f04 100644
--- a/cranelift/codegen/src/isa/s390x/inst.isle
+++ b/cranelift/codegen/src/isa/s390x/inst.isle
@@ -2975,9 +2975,9 @@
 
 ;; Helper to compute the type of an implicitly extended argument/return value.
 (decl abi_ext_ty (ArgumentExtension Type) Type)
-(rule (abi_ext_ty (ArgumentExtension.None) ty) ty)
-(rule (abi_ext_ty (ArgumentExtension.Uext) _) $I64)
-(rule (abi_ext_ty (ArgumentExtension.Sext) _) $I64)
+(rule 0 (abi_ext_ty _ ty) ty)
+(rule 1 (abi_ext_ty (ArgumentExtension.Uext) (gpr32_ty _)) $I64)
+(rule 1 (abi_ext_ty (ArgumentExtension.Sext) (gpr32_ty _)) $I64)
 
 
 ;; Helpers for generating immediate values ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index def41ade3..30fcd7c24 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -16,7 +16,7 @@ pub use crate::machinst::{
     ABIArg, ABIArgSlot, InputSourceInst, Lower, LowerBackend, RealReg, Reg, RelocDistance, Sig,
     VCodeInst, Writable,
 };
-pub use crate::settings::TlsModel;
+pub use crate::settings::{OptLevel, TlsModel};
 
 pub type Unit = ();
 pub type ValueSlice = (ValueList, usize);
@@ -131,7 +131,9 @@ macro_rules! isle_lower_prelude_methods {
             // use. This lowers register pressure. (Only do this if we are
             // not using egraph-based compilation; the egraph framework
             // more efficiently rematerializes constants where needed.)
-            if !self.backend.flags().use_egraphs() {
+            if !(self.backend.flags().use_egraphs()
+                && self.backend.flags().opt_level() != OptLevel::None)
+            {
                 let inputs = self.lower_ctx.get_value_as_source_or_const(val);
                 if inputs.constant.is_some() {
                     let insn = match inputs.inst {
diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs
index 27be5c17f..6836646e2 100644
--- a/cranelift/codegen/src/settings.rs
+++ b/cranelift/codegen/src/settings.rs
@@ -528,7 +528,7 @@ probestack_strategy = "outline"
 regalloc_checker = false
 regalloc_verbose_logs = false
 enable_alias_analysis = true
-use_egraphs = false
+use_egraphs = true
 enable_verifier = true
 is_pic = false
 use_colocated_libcalls = false
diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs
index 2383fbdb5..f98212c9c 100644
--- a/cranelift/fuzzgen/src/function_generator.rs
+++ b/cranelift/fuzzgen/src/function_generator.rs
@@ -502,6 +502,46 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
             )
         }
 
+        Architecture::S390x => {
+            exceptions!(
+                (Opcode::IaddCout),
+                (Opcode::Udiv, &[I128, I128]),
+                (Opcode::Sdiv, &[I128, I128]),
+                (Opcode::Urem, &[I128, I128]),
+                (Opcode::Srem, &[I128, I128]),
+                (Opcode::Smin),
+                (Opcode::Smax),
+                (Opcode::Umin),
+                (Opcode::Umax),
+                (Opcode::Band, &[F32, F32]),
+                (Opcode::Band, &[F64, F64]),
+                (Opcode::Bor, &[F32, F32]),
+                (Opcode::Bor, &[F64, F64]),
+                (Opcode::Bxor, &[F32, F32]),
+                (Opcode::Bxor, &[F64, F64]),
+                (Opcode::Bnot, &[F32, F32]),
+                (Opcode::Bnot, &[F64, F64]),
+                (Opcode::BandNot, &[F32, F32]),
+                (Opcode::BandNot, &[F64, F64]),
+                (Opcode::BorNot, &[F32, F32]),
+                (Opcode::BorNot, &[F64, F64]),
+                (Opcode::BxorNot, &[F32, F32]),
+                (Opcode::BxorNot, &[F64, F64]),
+                (Opcode::FcvtToUint, &[F32], &[I128]),
+                (Opcode::FcvtToUint, &[F64], &[I128]),
+                (Opcode::FcvtToUintSat, &[F32], &[I128]),
+                (Opcode::FcvtToUintSat, &[F64], &[I128]),
+                (Opcode::FcvtToSint, &[F32], &[I128]),
+                (Opcode::FcvtToSint, &[F64], &[I128]),
+                (Opcode::FcvtToSintSat, &[F32], &[I128]),
+                (Opcode::FcvtToSintSat, &[F64], &[I128]),
+                (Opcode::FcvtFromUint, &[I128], &[F32]),
+                (Opcode::FcvtFromUint, &[I128], &[F64]),
+                (Opcode::FcvtFromSint, &[I128], &[F32]),
+                (Opcode::FcvtFromSint, &[I128], &[F64]),
+            )
+        }
+
         Architecture::Riscv64(_) => {
             exceptions!(
                 // TODO
diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs
index 435abbb42..d9caff8f3 100644
--- a/crates/component-macro/src/bindgen.rs
+++ b/crates/component-macro/src/bindgen.rs
@@ -226,9 +226,23 @@ impl Parse for Opt {
 }
 
 fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<(String, String, String)> {
-    let interface = input.parse::<Ident>()?.to_string();
+    // Accept a Rust identifier or a string literal. This is required
+    // because not all wit identifiers are Rust identifiers, so we can
+    // smuggle the invalid ones inside quotes.
+    fn ident_or_str(input: ParseStream<'_>) -> Result<String> {
+        let l = input.lookahead1();
+        if l.peek(syn::LitStr) {
+            Ok(input.parse::<syn::LitStr>()?.value())
+        } else if l.peek(syn::Ident) {
+            Ok(input.parse::<syn::Ident>()?.to_string())
+        } else {
+            Err(l.error())
+        }
+    }
+
+    let interface = ident_or_str(input)?;
     input.parse::<Token![::]>()?;
-    let type_ = input.parse::<Ident>()?.to_string();
+    let type_ = ident_or_str(input)?;
     input.parse::<Token![:]>()?;
     let rust_type = input.parse::<Ident>()?.to_string();
     Ok((interface, type_, rust_type))
diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs
index b87fb6cb3..b054f993c 100644
--- a/crates/component-macro/src/component.rs
+++ b/crates/component-macro/src/component.rs
@@ -1114,6 +1114,14 @@ pub fn expand_flags(flags: &Flags) -> Result<TokenStream> {
                 use std::ops::Not;
                 Self::default().not()
             }
+
+            pub fn contains(&self, other: Self) -> bool {
+                *self & other == other
+            }
+
+            pub fn intersects(&self, other: Self) -> bool {
+                *self & other != Self::empty()
+            }
         }
 
         impl std::cmp::PartialEq for #name {
diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs
index 6b0d69eef..aaf435c01 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -97,31 +97,47 @@ impl Config {
         self.module_config.generate(input, default_fuel)
     }
 
-    /// Indicates that this configuration should be spec-test-compliant,
-    /// disabling various features the spec tests assert are disabled.
-    pub fn set_spectest_compliant(&mut self) {
-        let config = &mut self.module_config.config;
-        config.memory64_enabled = false;
-        config.bulk_memory_enabled = true;
-        config.reference_types_enabled = true;
-        config.multi_value_enabled = true;
-        config.simd_enabled = true;
-        config.threads_enabled = false;
-        config.max_memories = 1;
-        config.max_tables = 5;
+    /// Tests whether this configuration is capable of running all spec tests.
+    pub fn is_spectest_compliant(&self) -> bool {
+        let config = &self.module_config.config;
 
-        if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.wasmtime.strategy {
-            // Configure the lower bound of a number of limits to what's
-            // required to actually run the spec tests. Fuzz-generated inputs
-            // may have limits less than these thresholds which would cause the
-            // spec tests to fail which isn't particularly interesting.
-            pooling.instance_memories = 1;
-            pooling.instance_tables = pooling.instance_tables.max(5);
-            pooling.instance_table_elements = pooling.instance_table_elements.max(1_000);
-            pooling.instance_memory_pages = pooling.instance_memory_pages.max(900);
-            pooling.instance_count = pooling.instance_count.max(500);
-            pooling.instance_size = pooling.instance_size.max(64 * 1024);
+        // Check for wasm features that must be disabled to run spec tests
+        if config.memory64_enabled || config.threads_enabled {
+            return false;
+        }
+
+        // Check for wasm features that must be enabled to run spec tests
+        if !config.bulk_memory_enabled
+            || !config.reference_types_enabled
+            || !config.multi_value_enabled
+            || !config.simd_enabled
+        {
+            return false;
+        }
+
+        // Make sure the runtime limits allow for the instantiation of all spec
+        // tests. Note that the max memories must be precisely one since 0 won't
+        // instantiate spec tests and more than one is multi-memory which is
+        // disabled for spec tests.
+        if config.max_memories != 1 || config.max_tables < 5 {
+            return false;
         }
+
+        if let InstanceAllocationStrategy::Pooling(pooling) = &self.wasmtime.strategy {
+            // Check to see if any item limit is less than the required
+            // threshold to execute the spec tests.
+            if pooling.instance_memories < 1
+                || pooling.instance_tables < 5
+                || pooling.instance_table_elements < 1_000
+                || pooling.instance_memory_pages < 900
+                || pooling.instance_count < 500
+                || pooling.instance_size < 64 * 1024
+            {
+                return false;
+            }
+        }
+
+        true
     }
 
     /// Converts this to a `wasmtime::Config` object
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 37f389252..e453b2054 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -501,9 +501,11 @@ pub fn make_api_calls(api: generators::api::ApiCalls) {
 /// Executes the wast `test` spectest with the `config` specified.
 ///
 /// Ensures that spec tests pass regardless of the `Config`.
-pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest) {
+pub fn spectest(fuzz_config: generators::Config, test: generators::SpecTest) {
     crate::init_fuzzing();
-    fuzz_config.set_spectest_compliant();
+    if !fuzz_config.is_spectest_compliant() {
+        return;
+    }
     log::debug!("running {:?}", test.file);
     let mut wast_context = WastContext::new(fuzz_config.to_store());
     wast_context.register_spectest(false).unwrap();
diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml
index 5a21eb7d8..852939284 100644
--- a/crates/jit/Cargo.toml
+++ b/crates/jit/Cargo.toml
@@ -34,7 +34,7 @@ features = [
 ]
 
 [target.'cfg(target_arch = "x86_64")'.dependencies]
-ittapi = { version = "0.3.0", optional = true  }
+ittapi = { version = "0.3.3", optional = true  }
 
 [features]
 jitdump = ['wasmtime-jit-debug']
diff --git a/crates/runtime/src/traphandlers/unix.rs b/crates/runtime/src/traphandlers/unix.rs
index de7b335ce..889867ba1 100644
--- a/crates/runtime/src/traphandlers/unix.rs
+++ b/crates/runtime/src/traphandlers/unix.rs
@@ -224,6 +224,12 @@ unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const
                 cx.uc_mcontext.__gregs[libc::REG_PC] as *const u8,
                 cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
             )
+        } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
+            let cx = &*(cx as *const libc::mcontext_t);
+            (
+                cx.mc_gpregs.gp_elr as *const u8,
+                cx.mc_gpregs.gp_x[29] as usize,
+            )
         }
         else {
             compile_error!("unsupported platform");
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index b3c32f2bc..7e42afbc4 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -832,14 +832,13 @@ impl Config {
     /// Configures the Cranelift code generator to use its
     /// "egraph"-based mid-end optimizer.
     ///
-    /// This optimizer is intended to replace the compiler's more
-    /// traditional pipeline of optimization passes with a unified
-    /// code-rewriting system. It is not yet on by default, but it is
-    /// intended to become the default in a future version. It may
-    /// result in faster code, at the cost of slightly more
-    /// compilation-time work.
-    ///
-    /// The default value for this is `false`.
+    /// This optimizer has replaced the compiler's more traditional
+    /// pipeline of optimization passes with a unified code-rewriting
+    /// system. It is on by default, but the traditional optimization
+    /// pass structure is still available for now (it is deprecrated and
+    /// will be removed in a future version).
+    ///
+    /// The default value for this is `true`.
     #[cfg(compiler)]
     #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
     pub fn cranelift_use_egraphs(&mut self, enable: bool) -> &mut Self {
diff --git a/docs/lang-elixir.md b/docs/lang-elixir.md
new file mode 100644
index 000000000..d961f2ede
--- /dev/null
+++ b/docs/lang-elixir.md
@@ -0,0 +1,43 @@
+# Using WebAssembly from Elixir
+
+Wasmtime [is available on Hex](https://hex.pm/packages/wasmex) and can
+be used programmatically to interact with Wasm modules. This guide will go over
+installing the wasmex package and running a simple Wasm module from Elixir.
+
+## Getting started and simple example
+
+First, copy this example WebAssembly text module into the current directory. It exports
+a function for calculating the greatest common denominator of two numbers.
+
+```wat
+{{#include ../examples/gcd.wat}}
+```
+
+The library has a Rust-based native extension, but thanks to `rustler_precompiled`, you
+should not have to compile anything. It'll just work!
+
+This WAT file can be executed in `iex`:
+
+```elixir
+Mix.install([:wasmex])
+bytes = File.read!("gcd.wat")
+{:ok, pid} = Wasmex.start_link(%{bytes: bytes}) # starts a GenServer running a WASM instance
+Wasmex.call_function(pid, "gcd", [27, 6])
+```
+
+The last command should output:
+
+```elixir
+iex(5)> Wasmex.call_function(pid, "gcd", [27, 6])
+{:ok, [3]}
+```
+
+If this is the output you see, congrats! You've successfully ran your first
+WebAssembly code in Elixir!
+
+## More examples and contributing
+
+To learn more, check out an [another example](https://github.com/tessi/wasmex#example)
+and the [API documentation](https://hexdocs.pm/wasmex/Wasmex.html).
+If you have any questions, do not hesitate to open an issue on the
+[GitHub repository](https://github.com/tessi/wasmex).
diff --git a/docs/lang.md b/docs/lang.md
index ae3d0a13c..94e38d1b0 100644
--- a/docs/lang.md
+++ b/docs/lang.md
@@ -11,3 +11,4 @@ through a C API for a number of other languages too:
 * [Go](lang-go.md)
 * [Bash](lang-bash.md)
 * [Ruby](lang-ruby.md)
+* [Elixir](lang-elixir.md)
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 8ee455957..11b6ff3d0 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -10,7 +10,7 @@ cargo-fuzz = true
 [dependencies]
 anyhow = { workspace = true }
 once_cell = { workspace = true }
-cranelift-codegen = { workspace = true, features = ["incremental-cache", "x86", "arm64", "riscv64"] }
+cranelift-codegen = { workspace = true, features = ["incremental-cache", "x86", "arm64", "s390x", "riscv64"] }
 cranelift-reader = { workspace = true }
 cranelift-wasm = { workspace = true }
 cranelift-filetests = { workspace = true }
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 49d0fd477..e4054b437 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -290,6 +290,18 @@ criteria = "safe-to-deploy"
 version = "0.4.1"
 notes = "Contains only unsafe code for interacting with the crate's intended purpose."
 
+[[audits.ittapi]]
+who = "Andrew Brown <andrew.brown@intel.com>"
+criteria = "safe-to-deploy"
+delta = "0.3.1 -> 0.3.3"
+notes = "I am the author of this crate."
+
+[[audits.ittapi-sys]]
+who = "Andrew Brown <andrew.brown@intel.com>"
+criteria = "safe-to-deploy"
+delta = "0.3.1 -> 0.3.3"
+notes = "Unsafe code is due to auto-generated bindings to a widely-deployed C library."
+
 [[audits.leb128]]
 who = "Nick Fitzgerald <fitzgen@gmail.com>"
 criteria = "safe-to-deploy"
@@ -506,6 +518,16 @@ intended to multiplex across the internal representation of a tinyvec,
 presumably. This trivially doesn't contain anything bad.
 """
 
+[[audits.tokio]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+delta = "1.18.1 -> 1.18.4"
+notes = """
+This looks to be a minor release primarily to fix a security-related Windows
+issue plus some reorganization around lazy initialization. Altogether nothing
+amiss here.
+"""
+
 [[audits.unicase]]
 who = "Alex Crichton <alex@alexcrichton.com>"
 criteria = "safe-to-deploy"
diff --git a/winch/Cargo.toml b/winch/Cargo.toml
index 7f740e49b..d86a0b22a 100644
--- a/winch/Cargo.toml
+++ b/winch/Cargo.toml
@@ -11,9 +11,10 @@ edition.workspace = true
 name = "winch-tools"
 path = "src/main.rs"
 
-
 [dependencies]
 winch-codegen = { workspace = true }
+winch-filetests = { workspace = true }
+winch-test-macros = { workspace = true }
 wasmtime-environ = { workspace = true }
 target-lexicon = { workspace = true }
 anyhow = { workspace = true }
@@ -22,6 +23,10 @@ clap = { workspace = true }
 wat = { workspace = true }
 cranelift-codegen = { workspace = true }
 capstone = { workspace = true }
+similar = { workspace = true }
+toml = { workspace = true }
+serde = { workspace = true }
+glob = { workspace = true }
 
 [features]
 default = ["all-arch"]
diff --git a/winch/src/compile.rs b/winch/src/compile.rs
new file mode 100644
index 000000000..0f1c49239
--- /dev/null
+++ b/winch/src/compile.rs
@@ -0,0 +1,72 @@
+use anyhow::{Context, Result};
+use clap::Parser;
+use cranelift_codegen::settings;
+use std::{fs, path::PathBuf, str::FromStr};
+use target_lexicon::Triple;
+use wasmtime_environ::{
+    wasmparser::{types::Types, Parser as WasmParser, Validator},
+    DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
+};
+use winch_codegen::{lookup, TargetIsa};
+use winch_filetests::disasm::disasm;
+
+#[derive(Parser, Debug)]
+pub struct Options {
+    /// The input file.
+    input: PathBuf,
+
+    /// The target architecture.
+    #[clap(long = "target")]
+    target: String,
+}
+
+pub fn run(opt: &Options) -> Result<()> {
+    let bytes = fs::read(&opt.input)
+        .with_context(|| format!("Failed to read input file {}", opt.input.display()))?;
+    let bytes = wat::parse_bytes(&bytes)?;
+    let triple = Triple::from_str(&opt.target)?;
+    let shared_flags = settings::Flags::new(settings::builder());
+    let isa_builder = lookup(triple)?;
+    let isa = isa_builder.build(shared_flags)?;
+    let mut validator = Validator::new();
+    let parser = WasmParser::new(0);
+    let mut types = Default::default();
+    let tunables = Tunables::default();
+    let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
+        .translate(parser, &bytes)
+        .context("Failed to translate WebAssembly module")?;
+    let _ = types.finish();
+
+    let body_inputs = std::mem::take(&mut translation.function_body_inputs);
+    let module = &translation.module;
+    let types = translation.get_types();
+
+    body_inputs
+        .into_iter()
+        .try_for_each(|func| compile(&*isa, module, types, func))?;
+
+    Ok(())
+}
+
+fn compile(
+    isa: &dyn TargetIsa,
+    module: &Module,
+    types: &Types,
+    f: (DefinedFuncIndex, FunctionBodyData<'_>),
+) -> Result<()> {
+    let index = module.func_index(f.0);
+    let sig = types
+        .func_type_at(index.as_u32())
+        .expect(&format!("function type at index {:?}", index.as_u32()));
+    let FunctionBodyData { body, validator } = f.1;
+    let validator = validator.into_validator(Default::default());
+    let buffer = isa
+        .compile_function(&sig, &body, validator)
+        .expect("Couldn't compile function");
+
+    disasm(buffer.data(), isa)?
+        .iter()
+        .for_each(|s| println!("{}", s));
+
+    Ok(())
+}
diff --git a/winch/src/disasm.rs b/winch/src/disasm.rs
deleted file mode 100644
index bcb9924b4..000000000
--- a/winch/src/disasm.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! Disassembly utilities.
-
-use anyhow::{bail, Result};
-use capstone::prelude::*;
-use std::fmt::Write;
-use target_lexicon::Architecture;
-use winch_codegen::TargetIsa;
-
-/// Disassemble and print a machine code buffer.
-pub fn print(bytes: &[u8], isa: &dyn TargetIsa) -> Result<()> {
-    let dis = disassembler_for(isa)?;
-    let insts = dis.disasm_all(bytes, 0x0).unwrap();
-
-    for i in insts.iter() {
-        let mut line = String::new();
-
-        write!(&mut line, "{:4x}:\t", i.address()).unwrap();
-
-        let mut bytes_str = String::new();
-        let mut len = 0;
-        let mut first = true;
-        for b in i.bytes() {
-            if !first {
-                write!(&mut bytes_str, " ").unwrap();
-            }
-            write!(&mut bytes_str, "{:02x}", b).unwrap();
-            len += 1;
-            first = false;
-        }
-        write!(&mut line, "{:21}\t", bytes_str).unwrap();
-        if len > 8 {
-            write!(&mut line, "\n\t\t\t\t").unwrap();
-        }
-
-        if let Some(s) = i.mnemonic() {
-            write!(&mut line, "{}\t", s).unwrap();
-        }
-
-        if let Some(s) = i.op_str() {
-            write!(&mut line, "{}", s).unwrap();
-        }
-
-        println!("{}", line);
-    }
-
-    Ok(())
-}
-
-fn disassembler_for(isa: &dyn TargetIsa) -> Result<Capstone> {
-    let disasm = match isa.triple().architecture {
-        Architecture::X86_64 => Capstone::new()
-            .x86()
-            .mode(arch::x86::ArchMode::Mode64)
-            .build()
-            .map_err(|e| anyhow::format_err!("{}", e))?,
-
-        _ => bail!("Unsupported ISA"),
-    };
-
-    Ok(disasm)
-}
diff --git a/winch/src/main.rs b/winch/src/main.rs
index 3e88bbe7e..9984b86d3 100644
--- a/winch/src/main.rs
+++ b/winch/src/main.rs
@@ -1,77 +1,21 @@
-//! Winch CLI tool, meant mostly for testing purposes.
-//!
-//! Reads Wasm in binary/text format and compiles them
-//! to any of the supported architectures using Winch.
+mod compile;
+mod filetests;
 
-use anyhow::{Context, Result};
+use anyhow::Result;
 use clap::Parser;
-use cranelift_codegen::settings;
-use std::{fs, path::PathBuf, str::FromStr};
-use target_lexicon::Triple;
-use wasmtime_environ::{
-    wasmparser::{types::Types, Parser as WasmParser, Validator},
-    DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
-};
-use winch_codegen::{lookup, TargetIsa};
 
-mod disasm;
-
-#[derive(Parser, Debug)]
-struct Options {
-    /// The input file.
-    input: PathBuf,
-
-    /// The target architecture.
-    #[clap(long = "target")]
-    target: String,
+/// Winch compilation and testing tool.
+#[derive(Parser)]
+enum Commands {
+    /// Compile a Wasm module to the specified target architecture.
+    Compile(compile::Options),
+    /// Run the filetests.
+    Test(filetests::Options),
 }
 
 fn main() -> Result<()> {
-    let opt = Options::from_args();
-    let bytes = fs::read(&opt.input)
-        .with_context(|| format!("Failed to read input file {}", opt.input.display()))?;
-    let bytes = wat::parse_bytes(&bytes)?;
-    let triple = Triple::from_str(&opt.target)?;
-    let shared_flags = settings::Flags::new(settings::builder());
-    let isa_builder = lookup(triple)?;
-    let isa = isa_builder.build(shared_flags)?;
-    let mut validator = Validator::new();
-    let parser = WasmParser::new(0);
-    let mut types = Default::default();
-    let tunables = Tunables::default();
-    let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
-        .translate(parser, &bytes)
-        .context("Failed to translate WebAssembly module")?;
-    let _ = types.finish();
-
-    let body_inputs = std::mem::take(&mut translation.function_body_inputs);
-    let module = &translation.module;
-    let types = translation.get_types();
-
-    body_inputs
-        .into_iter()
-        .try_for_each(|func| compile(&*isa, module, types, func))?;
-
-    Ok(())
-}
-
-fn compile(
-    isa: &dyn TargetIsa,
-    module: &Module,
-    types: &Types,
-    f: (DefinedFuncIndex, FunctionBodyData<'_>),
-) -> Result<()> {
-    let index = module.func_index(f.0);
-    let sig = types
-        .func_type_at(index.as_u32())
-        .expect(&format!("function type at index {:?}", index.as_u32()));
-    let FunctionBodyData { body, validator } = f.1;
-    let validator = validator.into_validator(Default::default());
-    let buffer = isa
-        .compile_function(&sig, &body, validator)
-        .expect("Couldn't compile function");
-
-    disasm::print(buffer.data(), isa)?;
-
-    Ok(())
+    match Commands::parse() {
+        Commands::Compile(c) => compile::run(&c),
+        Commands::Test(t) => filetests::run(&t),
+    }
 }


Test patch:
diff --git a/cranelift/filetests/Cargo.toml b/cranelift/filetests/Cargo.toml
index 872326ad5..9af750b6a 100644
--- a/cranelift/filetests/Cargo.toml
+++ b/cranelift/filetests/Cargo.toml
@@ -26,10 +26,10 @@ num_cpus = "1.8.0"
 target-lexicon = { workspace = true }
 thiserror = { workspace = true }
 anyhow = { workspace = true }
-similar = "2.1.0"
+similar ={ workspace = true }
 wat.workspace = true
-toml = "0.5.9"
-serde = "1.0.94"
+toml = { workspace = true }
+serde = { workspace = true }
 cranelift-wasm.workspace = true
 wasmparser.workspace = true
 cranelift.workspace = true
diff --git a/cranelift/filetests/filetests/egraph/algebraic.clif b/cranelift/filetests/filetests/egraph/algebraic.clif
index 6eaa6fcda..3a852b0eb 100644
--- a/cranelift/filetests/filetests/egraph/algebraic.clif
+++ b/cranelift/filetests/filetests/egraph/algebraic.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/alias_analysis.clif b/cranelift/filetests/filetests/egraph/alias_analysis.clif
index ce7843146..87bc50736 100644
--- a/cranelift/filetests/filetests/egraph/alias_analysis.clif
+++ b/cranelift/filetests/filetests/egraph/alias_analysis.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/basic-gvn.clif b/cranelift/filetests/filetests/egraph/basic-gvn.clif
index 7b3878622..823614ea2 100644
--- a/cranelift/filetests/filetests/egraph/basic-gvn.clif
+++ b/cranelift/filetests/filetests/egraph/basic-gvn.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/cprop.clif b/cranelift/filetests/filetests/egraph/cprop.clif
index bc76e1d54..261be6fdf 100644
--- a/cranelift/filetests/filetests/egraph/cprop.clif
+++ b/cranelift/filetests/filetests/egraph/cprop.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/i128-opts.clif b/cranelift/filetests/filetests/egraph/i128-opts.clif
index 24737e2c9..f30b80bd2 100644
--- a/cranelift/filetests/filetests/egraph/i128-opts.clif
+++ b/cranelift/filetests/filetests/egraph/i128-opts.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=speed_and_size
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/isplit.clif b/cranelift/filetests/filetests/egraph/isplit.clif
index e5cb3ea49..e40c32fef 100644
--- a/cranelift/filetests/filetests/egraph/isplit.clif
+++ b/cranelift/filetests/filetests/egraph/isplit.clif
@@ -1,6 +1,6 @@
 test interpret
 test run
-set opt_level=speed_and_size
+set opt_level=speed
 set use_egraphs=true
 set enable_llvm_abi_extensions=true
 target x86_64
diff --git a/cranelift/filetests/filetests/egraph/issue-5405.clif b/cranelift/filetests/filetests/egraph/issue-5405.clif
index c453875de..db6f582ec 100644
--- a/cranelift/filetests/filetests/egraph/issue-5405.clif
+++ b/cranelift/filetests/filetests/egraph/issue-5405.clif
@@ -1,6 +1,6 @@
 test interpret
 test run
-set opt_level=speed_and_size
+set opt_level=speed
 set use_egraphs=true
 target aarch64
 
diff --git a/cranelift/filetests/filetests/egraph/issue-5437.clif b/cranelift/filetests/filetests/egraph/issue-5437.clif
index b8eb3c938..0f20d2f5a 100644
--- a/cranelift/filetests/filetests/egraph/issue-5437.clif
+++ b/cranelift/filetests/filetests/egraph/issue-5437.clif
@@ -1,4 +1,5 @@
 test compile
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 target aarch64
diff --git a/cranelift/filetests/filetests/egraph/licm.clif b/cranelift/filetests/filetests/egraph/licm.clif
index a6f458556..3b8b62514 100644
--- a/cranelift/filetests/filetests/egraph/licm.clif
+++ b/cranelift/filetests/filetests/egraph/licm.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/misc.clif b/cranelift/filetests/filetests/egraph/misc.clif
index 668c643cd..811211601 100644
--- a/cranelift/filetests/filetests/egraph/misc.clif
+++ b/cranelift/filetests/filetests/egraph/misc.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/multivalue.clif b/cranelift/filetests/filetests/egraph/multivalue.clif
index f2e2e1147..261fbc75a 100644
--- a/cranelift/filetests/filetests/egraph/multivalue.clif
+++ b/cranelift/filetests/filetests/egraph/multivalue.clif
@@ -1,4 +1,5 @@
 test compile precise-output
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/not_a_load.clif b/cranelift/filetests/filetests/egraph/not_a_load.clif
index fde8c2d0e..99eefacca 100644
--- a/cranelift/filetests/filetests/egraph/not_a_load.clif
+++ b/cranelift/filetests/filetests/egraph/not_a_load.clif
@@ -1,4 +1,5 @@
 test compile precise-output
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/egraph/remat.clif b/cranelift/filetests/filetests/egraph/remat.clif
index 69289b7cd..f1ba8dd20 100644
--- a/cranelift/filetests/filetests/egraph/remat.clif
+++ b/cranelift/filetests/filetests/egraph/remat.clif
@@ -1,5 +1,5 @@
 test optimize
-set opt_level=none
+set opt_level=speed
 set use_egraphs=true
 target x86_64
 
diff --git a/cranelift/filetests/filetests/isa/riscv64/condops.clif b/cranelift/filetests/filetests/isa/riscv64/condops.clif
index baa894f28..951b808d6 100644
--- a/cranelift/filetests/filetests/isa/riscv64/condops.clif
+++ b/cranelift/filetests/filetests/isa/riscv64/condops.clif
@@ -77,7 +77,8 @@ block0(v0: i8, v1: i128, v2: i128):
 }
 
 ; block0:
+;   mv a7,a1
 ;   andi a5,a0,255
-;   select_i128 [a0,a1],[a1,a2],[a3,a4]##condition=a5
+;   select_i128 [a0,a1],[a7,a2],[a3,a4]##condition=a5
 ;   ret
 
diff --git a/cranelift/filetests/filetests/isa/riscv64/extend-op.clif b/cranelift/filetests/filetests/isa/riscv64/extend-op.clif
index 595ef6a8d..9dfc4f2c4 100644
--- a/cranelift/filetests/filetests/isa/riscv64/extend-op.clif
+++ b/cranelift/filetests/filetests/isa/riscv64/extend-op.clif
@@ -66,8 +66,8 @@ block0(v0: i32):
 }
 
 ; block0:
-;   sext.w t2,a0
-;   slt a1,t2,zero
+;   sext.w a0,a0
+;   slt a1,a0,zero
 ;   sext.b1 a1,a1
 ;   ret
 
@@ -89,8 +89,8 @@ block0(v0: i16):
 }
 
 ; block0:
-;   sext.h t2,a0
-;   slt a1,t2,zero
+;   sext.h a0,a0
+;   slt a1,a0,zero
 ;   sext.b1 a1,a1
 ;   ret
 
@@ -112,8 +112,8 @@ block0(v0: i8):
 }
 
 ; block0:
-;   sext.b t2,a0
-;   slt a1,t2,zero
+;   sext.b a0,a0
+;   slt a1,a0,zero
 ;   sext.b1 a1,a1
 ;   ret
 
diff --git a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat
index bb4b66f54..fd74f5bae 100644
--- a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat
+++ b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat
@@ -45,8 +45,8 @@
 ;;   addi a5,a5,4092
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   sw a1,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   sw a1,0(t4)
 ;;   j label1
 ;; block1:
 ;;   ret
@@ -59,8 +59,8 @@
 ;;   addi a5,a5,4092
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   lw a0,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   lw a0,0(t4)
 ;;   j label1
 ;; block1:
-;;   ret
\ No newline at end of file
+;;   ret
diff --git a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat
index 37f86c762..d98101b57 100644
--- a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat
+++ b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat
@@ -45,8 +45,8 @@
 ;;   addi a5,a5,4095
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   sb a1,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   sb a1,0(t4)
 ;;   j label1
 ;; block1:
 ;;   ret
@@ -59,8 +59,8 @@
 ;;   addi a5,a5,4095
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   lbu a0,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   lbu a0,0(t4)
 ;;   j label1
 ;; block1:
-;;   ret
\ No newline at end of file
+;;   ret
diff --git a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat
index dd24c07a1..ba3a16065 100644
--- a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat
+++ b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat
@@ -45,8 +45,8 @@
 ;;   addi a5,a5,4092
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   sw a1,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   sw a1,0(t4)
 ;;   j label1
 ;; block1:
 ;;   ret
@@ -59,8 +59,8 @@
 ;;   addi a5,a5,4092
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   lw a0,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   lw a0,0(t4)
 ;;   j label1
 ;; block1:
-;;   ret
\ No newline at end of file
+;;   ret
diff --git a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat
index ada412053..82d8bbc05 100644
--- a/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat
+++ b/cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat
@@ -45,8 +45,8 @@
 ;;   addi a5,a5,4095
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   sb a1,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   sb a1,0(t4)
 ;;   j label1
 ;; block1:
 ;;   ret
@@ -59,8 +59,8 @@
 ;;   addi a5,a5,4095
 ;;   ugt t3,a0,a5##ty=i64
 ;;   li t0,0
-;;   selectif_spectre_guard a7,t0,a7##test=t3
-;;   lbu a0,0(a7)
+;;   selectif_spectre_guard t4,t0,a7##test=t3
+;;   lbu a0,0(t4)
 ;;   j label1
 ;; block1:
-;;   ret
\ No newline at end of file
+;;   ret
diff --git a/cranelift/filetests/filetests/runtests/issue5497.clif b/cranelift/filetests/filetests/runtests/issue5497.clif
new file mode 100644
index 000000000..d035d40aa
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue5497.clif
@@ -0,0 +1,11 @@
+test interpret
+test run
+target riscv64
+
+function %a(i16, i128) -> i128 system_v {
+block0(v0: i16, v1: i128):
+    v2 = smin v1, v1
+    return v2
+}
+
+; run: %a(0, 1) == 1
\ No newline at end of file
diff --git a/cranelift/filetests/filetests/runtests/issue5524.clif b/cranelift/filetests/filetests/runtests/issue5524.clif
new file mode 100644
index 000000000..3a8c1bd46
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue5524.clif
@@ -0,0 +1,11 @@
+test interpret
+test run
+target riscv64
+
+function %a(i128, i8, i8, i8, i32, i32, i8, i8, i64, i8) -> i8, i8, i8, i128 system_v {
+block0(v0: i128, v1: i8, v2: i8, v3: i8, v4: i32, v5: i32, v6: i8, v7: i8, v8: i64, v9: i8):
+    v16 = select v8, v0, v0
+    return v1, v1, v1, v16
+}
+
+; run: %a(65280, 0, 0, 0, 0, 0, 0, 0, 0, 0) == [0, 0, 0, 65280]
\ No newline at end of file
diff --git a/cranelift/filetests/filetests/runtests/issue5525.clif b/cranelift/filetests/filetests/runtests/issue5525.clif
new file mode 100644
index 000000000..1b34da2cb
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue5525.clif
@@ -0,0 +1,12 @@
+test interpret
+test run
+target riscv64
+
+function %a(i16) -> i128 system_v {
+block0(v0: i16):
+    v1 = rotl v0, v0
+    v2 = sextend.i128 v1
+    return v2
+}
+
+; run: %a(-32718) == 202
\ No newline at end of file
diff --git a/cranelift/filetests/filetests/runtests/issue5526.clif b/cranelift/filetests/filetests/runtests/issue5526.clif
new file mode 100644
index 000000000..f91dec0fa
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue5526.clif
@@ -0,0 +1,98 @@
+test interpret
+test run
+target riscv64
+
+function %a(i128, i8, i8, i8, i16, i32, i8, i8, i64, i8) -> i8, i8, i8, i128, i8, i8, i64, i128, i8 system_v {
+    ss0 = explicit_slot 90
+    ss1 = explicit_slot 90
+    ss2 = explicit_slot 90
+    ss3 = explicit_slot 90
+    ss4 = explicit_slot 126
+    ss5 = explicit_slot 126
+    ss6 = explicit_slot 126
+    ss7 = explicit_slot 126
+
+block0(v0: i128, v1: i8, v2: i8, v3: i8, v4: i16, v5: i32, v6: i8, v7: i8, v8: i64, v9: i8):
+    v11 = iconst.i8 50
+    v12 = iconst.i8 0
+    v13 = iconst.i16 0
+    v14 = iconst.i32 0
+    v15 = iconst.i64 0
+    v16 = uextend.i128 v15  ; v15 = 0
+    stack_store v16, ss0
+    stack_store v16, ss0+16
+    stack_store v16, ss0+32
+    stack_store v16, ss0+48
+    stack_store v16, ss0+64
+    stack_store v15, ss0+80  ; v15 = 0
+    stack_store v13, ss0+88  ; v13 = 0
+    stack_store v16, ss1
+    stack_store v16, ss1+16
+    stack_store v16, ss1+32
+    stack_store v16, ss1+48
+    stack_store v16, ss1+64
+    stack_store v15, ss1+80  ; v15 = 0
+    stack_store v13, ss1+88  ; v13 = 0
+    stack_store v16, ss2
+    stack_store v16, ss2+16
+    stack_store v16, ss2+32
+    stack_store v16, ss2+48
+    stack_store v16, ss2+64
+    stack_store v15, ss2+80  ; v15 = 0
+    stack_store v13, ss2+88  ; v13 = 0
+    stack_store v16, ss3
+    stack_store v16, ss3+16
+    stack_store v16, ss3+32
+    stack_store v16, ss3+48
+    stack_store v16, ss3+64
+    stack_store v15, ss3+80  ; v15 = 0
+    stack_store v13, ss3+88  ; v13 = 0
+    stack_store v16, ss4
+    stack_store v16, ss4+16
+    stack_store v16, ss4+32
+    stack_store v16, ss4+48
+    stack_store v16, ss4+64
+    stack_store v16, ss4+80
+    stack_store v16, ss4+96
+    stack_store v15, ss4+112  ; v15 = 0
+    stack_store v14, ss4+120  ; v14 = 0
+    stack_store v13, ss4+124  ; v13 = 0
+    stack_store v16, ss5
+    stack_store v16, ss5+16
+    stack_store v16, ss5+32
+    stack_store v16, ss5+48
+    stack_store v16, ss5+64
+    stack_store v16, ss5+80
+    stack_store v16, ss5+96
+    stack_store v15, ss5+112  ; v15 = 0
+    stack_store v14, ss5+120  ; v14 = 0
+    stack_store v13, ss5+124  ; v13 = 0
+    stack_store v16, ss6
+    stack_store v16, ss6+16
+    stack_store v16, ss6+32
+    stack_store v16, ss6+48
+    stack_store v16, ss6+64
+    stack_store v16, ss6+80
+    stack_store v16, ss6+96
+    stack_store v15, ss6+112  ; v15 = 0
+    stack_store v14, ss6+120  ; v14 = 0
+    stack_store v13, ss6+124  ; v13 = 0
+    stack_store v16, ss7
+    stack_store v16, ss7+16
+    stack_store v16, ss7+32
+    stack_store v16, ss7+48
+    stack_store v16, ss7+64
+    stack_store v16, ss7+80
+    stack_store v16, ss7+96
+    stack_store v15, ss7+112  ; v15 = 0
+    stack_store v14, ss7+120  ; v14 = 0
+    stack_store v13, ss7+124  ; v13 = 0
+
+
+    v17 = select_spectre_guard v8, v0, v0
+    v18 = isub v8, v8
+
+    return v1, v3, v2, v17, v1, v1, v18, v17, v6
+}
+
+; run: %a(64324483005384539584200704, 0, 0, 95, 24415, 1600085839, 1, 0, 89294900846985228, 4) == [0, 95, 0, 64324483005384539584200704, 0, 0, 0, 64324483005384539584200704, 1]
diff --git a/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory-egraph.wat b/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory-egraph.wat
new file mode 100644
index 000000000..b6d4e88dc
--- /dev/null
+++ b/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory-egraph.wat
@@ -0,0 +1,92 @@
+;;! target = "x86_64"
+;;!
+;;! optimize = true
+;;!
+;;! settings = [
+;;!   "enable_heap_access_spectre_mitigation=true",
+;;!   "opt_level=speed_and_size",
+;;!   "use_egraphs=true"
+;;! ]
+;;!
+;;! [globals.vmctx]
+;;! type = "i64"
+;;! vmctx = true
+;;!
+;;! [globals.heap_base]
+;;! type = "i64"
+;;! load = { base = "vmctx", offset = 0 }
+;;!
+;;! [globals.heap_bound]
+;;! type = "i64"
+;;! load = { base = "vmctx", offset = 8 }
+;;!
+;;! [[heaps]]
+;;! base = "heap_base"
+;;! min_size = 0
+;;! offset_guard_size = 0xffffffff
+;;! index_type = "i32"
+;;! style = { kind = "dynamic", bound = "heap_bound" }
+
+(module
+  (memory (export "memory") 0)
+  (func (export "load-without-offset") (param i32) (result i32 i32)
+    local.get 0
+    i32.load
+    local.get 0
+    i32.load
+  )
+  (func (export "load-with-offset") (param i32) (result i32 i32)
+    local.get 0
+    i32.load offset=1234
+    local.get 0
+    i32.load offset=1234
+  )
+)
+
+;; function u0:0(i32, i64 vmctx) -> i32, i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned gv0+8
+;;     gv2 = load.i64 notrap aligned gv0
+;;
+;;                                 block0(v0: i32, v1: i64):
+;; @0057                               v4 = uextend.i64 v0
+;; @0057                               v5 = iconst.i64 4
+;; @0057                               v6 = uadd_overflow_trap v4, v5, heap_oob  ; v5 = 4
+;; @0057                               v7 = load.i64 notrap aligned v1+8
+;; @0057                               v8 = load.i64 notrap aligned v1
+;; @0057                               v11 = icmp ugt v6, v7
+;; @0057                               v10 = iconst.i64 0
+;; @0057                               v9 = iadd v8, v4
+;; @0057                               v12 = select_spectre_guard v11, v10, v9  ; v10 = 0
+;; @0057                               v13 = load.i32 little heap v12
+;;                                     v2 -> v13
+;; @005f                               jump block1
+;;
+;;                                 block1:
+;; @005f                               return v13, v13
+;; }
+;;
+;; function u0:1(i32, i64 vmctx) -> i32, i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned gv0+8
+;;     gv2 = load.i64 notrap aligned gv0
+;;
+;;                                 block0(v0: i32, v1: i64):
+;; @0064                               v4 = uextend.i64 v0
+;; @0064                               v5 = iconst.i64 1238
+;; @0064                               v6 = uadd_overflow_trap v4, v5, heap_oob  ; v5 = 1238
+;; @0064                               v7 = load.i64 notrap aligned v1+8
+;; @0064                               v8 = load.i64 notrap aligned v1
+;; @0064                               v12 = icmp ugt v6, v7
+;; @0064                               v11 = iconst.i64 0
+;; @0064                               v9 = iadd v8, v4
+;;                                     v26 = iconst.i64 1234
+;; @0064                               v10 = iadd v9, v26  ; v26 = 1234
+;; @0064                               v13 = select_spectre_guard v12, v11, v10  ; v11 = 0
+;; @0064                               v14 = load.i32 little heap v13
+;;                                     v2 -> v14
+;; @006e                               jump block1
+;;
+;;                                 block1:
+;; @006e                               return v14, v14
+;; }
diff --git a/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat b/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat
index aab07174d..ac5d606cd 100644
--- a/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat
+++ b/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat
@@ -4,7 +4,8 @@
 ;;!
 ;;! settings = [
 ;;!   "enable_heap_access_spectre_mitigation=true",
-;;!   "opt_level=speed_and_size"
+;;!   "opt_level=speed_and_size",
+;;!   "use_egraphs=false"
 ;;! ]
 ;;!
 ;;! [globals.vmctx]
@@ -112,4 +113,4 @@
 ;;
 ;;                                 block1:
 ;; @006e                               return v14, v14
-;; }
\ No newline at end of file
+;; }
diff --git a/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory-egraph.wat b/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory-egraph.wat
new file mode 100644
index 000000000..d434d5a33
--- /dev/null
+++ b/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory-egraph.wat
@@ -0,0 +1,74 @@
+;;! target = "x86_64"
+;;!
+;;! optimize = true
+;;!
+;;! settings = [
+;;!   "enable_heap_access_spectre_mitigation=true",
+;;!   "opt_level=speed_and_size",
+;;!   "use_egraphs=true"
+;;! ]
+;;!
+;;! [globals.vmctx]
+;;! type = "i64"
+;;! vmctx = true
+;;!
+;;! [globals.heap_base]
+;;! type = "i64"
+;;! load = { base = "vmctx", offset = 0, readonly = true }
+;;!
+;;! [[heaps]]
+;;! base = "heap_base"
+;;! min_size = 0x10000
+;;! offset_guard_size = 0xffffffff
+;;! index_type = "i32"
+;;! style = { kind = "static", bound = 0x10000000 }
+
+(module
+  (memory (export "memory") 1)
+  (func (export "load-without-offset") (param i32) (result i32 i32)
+    local.get 0
+    i32.load
+    local.get 0
+    i32.load
+  )
+  (func (export "load-with-offset") (param i32) (result i32 i32)
+    local.get 0
+    i32.load offset=1234
+    local.get 0
+    i32.load offset=1234
+  )
+)
+
+;; function u0:0(i32, i64 vmctx) -> i32, i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0
+;;
+;;                                 block0(v0: i32, v1: i64):
+;; @0057                               v5 = load.i64 notrap aligned readonly v1
+;; @0057                               v4 = uextend.i64 v0
+;; @0057                               v6 = iadd v5, v4
+;; @0057                               v7 = load.i32 little heap v6
+;;                                     v2 -> v7
+;; @005f                               jump block1
+;;
+;;                                 block1:
+;; @005f                               return v7, v7
+;; }
+;;
+;; function u0:1(i32, i64 vmctx) -> i32, i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0
+;;
+;;                                 block0(v0: i32, v1: i64):
+;; @0064                               v5 = load.i64 notrap aligned readonly v1
+;; @0064                               v4 = uextend.i64 v0
+;; @0064                               v6 = iadd v5, v4
+;;                                     v14 = iconst.i64 1234
+;; @0064                               v7 = iadd v6, v14  ; v14 = 1234
+;; @0064                               v8 = load.i32 little heap v7
+;;                                     v2 -> v8
+;; @006e                               jump block1
+;;
+;;                                 block1:
+;; @006e                               return v8, v8
+;; }
diff --git a/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory.wat b/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory.wat
index 6a82e60b1..bef294915 100644
--- a/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory.wat
+++ b/cranelift/filetests/filetests/wasm/duplicate-loads-static-memory.wat
@@ -4,7 +4,8 @@
 ;;!
 ;;! settings = [
 ;;!   "enable_heap_access_spectre_mitigation=true",
-;;!   "opt_level=speed_and_size"
+;;!   "opt_level=speed_and_size",
+;;!   "use_egraphs=false"
 ;;! ]
 ;;!
 ;;! [globals.vmctx]
diff --git a/tests/all/component_model/bindgen/results.rs b/tests/all/component_model/bindgen/results.rs
index 995a2e528..82e318948 100644
--- a/tests/all/component_model/bindgen/results.rs
+++ b/tests/all/component_model/bindgen/results.rs
@@ -373,7 +373,9 @@ mod record_error {
                 record-error: func(a: float64) -> result<float64, e2>
             }
         }",
-        trappable_error_type: { imports::e2: TrappableE2 }
+        // Literal strings can be used for the interface and typename fields instead of
+        // identifiers, because wit identifiers arent always Rust identifiers.
+        trappable_error_type: { "imports"::"e2": TrappableE2 }
     });
 
     #[test]
diff --git a/winch/docs/testing.md b/winch/docs/testing.md
new file mode 100644
index 000000000..859db45b7
--- /dev/null
+++ b/winch/docs/testing.md
@@ -0,0 +1,63 @@
+# Testing Winch
+
+Winch is tested through integration testing using the `winch-filetests` crate
+and manual exploratory testing. A CLI is available to run these tests
+conveniently. To add the `winch-tools` binary to your `PATH`, run `cargo install
+--path winch` from the root of `wasmtime`. The CLI provides two commands: `test`
+and `compile`. To see the help text for each command, run `winch-tools test
+--help` or `winch-tools compile --help`.
+
+## Integration Testing (`winch-tools test`)
+
+The `test` command will run a suite of tests that validates Winch output for a
+WebAssembly module is consistent with our expectations.
+
+### Running `test`
+
+Running `winch-tools test` will run all integration tests in the
+`winch-filetests` crate. All arguments following two dashes (`--`) will be
+passed directly to `cargo test -p winch-filetests`. This will allow you to
+configure the tests to run based on your requirements. All tests in the
+`winch-filetests` crate get named in the following convention:
+`winch_filetests_${filepath}`. This makes it possible to filter if you don't
+want to run the entire suite.
+
+If the output of Winch changes for a test in a run due to code updates, the test
+will fail and the difference between the two outputs will be shown. If the new
+output is expected, the tests can be re-run with an `WINCH_TEST_BLESS`
+environment variable set to `1`.
+
+### Adding a test
+
+To add new tests, create a `.wat` file in the `winch/filetests/filetests` folder
+in the following format:
+
+```wat
+;;! target = "x86_64"
+(module
+  (func (result i32)
+    (i32.const 42)
+  )
+)
+```
+
+It is encouraged to use folders to organize tests. For example, tests targeting
+the x86_64 architecture can be placed in the `winch/filetests/filetests/x64`.
+
+The first block of comments are a TOML compatible configuration passed to Winch
+during compilation with a `!` at the start of each line. The body of the file
+will be the subject of the test. A final block of comments is reserved for the
+output of the compilation, and it will be used to compare the output of the
+current run with the output of previous runs.
+
+## Manual Exploratory Tests (`winch-tools compile`)
+
+The `compile` command will run Winch for particular architecture against
+provided input file, and print the disassembled output to the console. Only
+`.wat` files are supported.
+
+### Running `compile`
+
+```bash
+winch-tools compile $wat_file --target $target_triple
+```
diff --git a/winch/filetests/Cargo.toml b/winch/filetests/Cargo.toml
new file mode 100644
index 000000000..9250078b4
--- /dev/null
+++ b/winch/filetests/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+authors = ["The Winch Project Developers"]
+name = "winch-filetests"
+description = "Tests for the Winch compiler based on a set of known valid files"
+license = "Apache-2.0 WITH LLVM-exception"
+repository = "https://github.com/bytecodealliance/wasmtime"
+version = "0.0.0"
+publish = false
+edition.workspace = true
+
+[dependencies]
+winch-test-macros = {workspace = true}
+target-lexicon = { workspace = true }
+winch-codegen = { workspace = true, features = ['all-arch'] }
+wasmtime-environ = { workspace = true }
+anyhow = { workspace = true }
+wat = { workspace = true }
+similar = { workspace = true }
+toml = { workspace = true }
+serde = { workspace = true }
+cranelift-codegen = { workspace = true }
+capstone = { workspace = true }
diff --git a/winch/filetests/filetests/x64/simple.wat b/winch/filetests/filetests/x64/simple.wat
new file mode 100644
index 000000000..c839e1f31
--- /dev/null
+++ b/winch/filetests/filetests/x64/simple.wat
@@ -0,0 +1,12 @@
+;;! target = "x86_64"
+
+(module
+  (func (result i32)
+    (i32.const 42)
+  )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 48c7c02a000000       	mov	rax, 0x2a
+;;    b:	 5d                   	pop	rbp
+;;    c:	 c3                   	ret
diff --git a/winch/filetests/src/disasm.rs b/winch/filetests/src/disasm.rs
new file mode 100644
index 000000000..ee75e4997
--- /dev/null
+++ b/winch/filetests/src/disasm.rs
@@ -0,0 +1,59 @@
+//! Disassembly utilities.
+
+use anyhow::{bail, Result};
+use capstone::prelude::*;
+use std::fmt::Write;
+use target_lexicon::Architecture;
+use winch_codegen::TargetIsa;
+
+/// Disassemble and print a machine code buffer.
+pub fn disasm(bytes: &[u8], isa: &dyn TargetIsa) -> Result<Vec<String>> {
+    let dis = disassembler_for(isa)?;
+    let insts = dis.disasm_all(bytes, 0x0).unwrap();
+
+    let disassembled_lines = insts
+        .iter()
+        .map(|i| {
+            let mut line = String::new();
+
+            write!(&mut line, "{:4x}:\t ", i.address()).unwrap();
+
+            let mut bytes_str = String::new();
+            let mut len = 0;
+            for b in i.bytes() {
+                write!(&mut bytes_str, "{:02x}", b).unwrap();
+                len += 1;
+            }
+            write!(&mut line, "{:21}\t", bytes_str).unwrap();
+            if len > 8 {
+                write!(&mut line, "\n\t\t\t\t").unwrap();
+            }
+
+            if let Some(s) = i.mnemonic() {
+                write!(&mut line, "{}\t", s).unwrap();
+            }
+
+            if let Some(s) = i.op_str() {
+                write!(&mut line, "{}", s).unwrap();
+            }
+
+            line
+        })
+        .collect();
+
+    Ok(disassembled_lines)
+}
+
+fn disassembler_for(isa: &dyn TargetIsa) -> Result<Capstone> {
+    let disasm = match isa.triple().architecture {
+        Architecture::X86_64 => Capstone::new()
+            .x86()
+            .mode(arch::x86::ArchMode::Mode64)
+            .build()
+            .map_err(|e| anyhow::format_err!("{}", e))?,
+
+        _ => bail!("Unsupported ISA"),
+    };
+
+    Ok(disasm)
+}
diff --git a/winch/filetests/src/lib.rs b/winch/filetests/src/lib.rs
new file mode 100644
index 000000000..225445a2a
--- /dev/null
+++ b/winch/filetests/src/lib.rs
@@ -0,0 +1,165 @@
+pub mod disasm;
+
+#[cfg(test)]
+mod test {
+    use super::disasm::disasm;
+    use anyhow::Context;
+    use cranelift_codegen::settings;
+    use serde::{Deserialize, Serialize};
+    use similar::TextDiff;
+    use std::str::FromStr;
+    use target_lexicon::Triple;
+    use wasmtime_environ::{
+        wasmparser::{types::Types, Parser as WasmParser, Validator},
+        DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
+    };
+    use winch_codegen::isa::TargetIsa;
+    use winch_codegen::lookup;
+    use winch_test_macros::generate_file_tests;
+
+    #[derive(Clone, Debug, Serialize, Deserialize)]
+    struct TestConfig {
+        target: String,
+    }
+
+    /// A helper function to parse the test configuration from the top of the file.
+    fn parse_config(wat: &str) -> TestConfig {
+        let config_lines: Vec<_> = wat
+            .lines()
+            .take_while(|l| l.starts_with(";;!"))
+            .map(|l| &l[3..])
+            .collect();
+        let config_text = config_lines.join("\n");
+
+        toml::from_str(&config_text)
+            .context("failed to parse the test configuration")
+            .unwrap()
+    }
+
+    /// A helper function to parse the expected result from the bottom of the file.
+    fn parse_expected_result(wat: &str) -> String {
+        let mut expected_lines: Vec<_> = wat
+            .lines()
+            .rev()
+            .take_while(|l| l.starts_with(";;"))
+            .map(|l| {
+                if l.starts_with(";; ") {
+                    &l[3..]
+                } else {
+                    &l[2..]
+                }
+            })
+            .collect();
+        expected_lines.reverse();
+        expected_lines.join("\n")
+    }
+
+    /// A helper function to rewrite the expected result in the file.
+    fn rewrite_expected(wat: &str, actual: &str) -> String {
+        let old_expectation_line_count = wat
+            .lines()
+            .rev()
+            .take_while(|l| l.starts_with(";;"))
+            .count();
+        let old_wat_line_count = wat.lines().count();
+        let new_wat_lines: Vec<_> = wat
+            .lines()
+            .take(old_wat_line_count - old_expectation_line_count)
+            .map(|l| l.to_string())
+            .chain(actual.lines().map(|l| {
+                if l.is_empty() {
+                    ";;".to_string()
+                } else {
+                    format!(";; {l}")
+                }
+            }))
+            .collect();
+        let mut new_wat = new_wat_lines.join("\n");
+        new_wat.push('\n');
+
+        new_wat
+    }
+
+    #[generate_file_tests]
+    fn run_test(test_path: &str) {
+        let binding = std::fs::read_to_string(test_path).unwrap();
+        let wat = binding.as_str();
+
+        let config = parse_config(wat);
+        let wasm = wat::parse_str(&wat).unwrap();
+        let triple = Triple::from_str(&config.target).unwrap();
+
+        let binding = parse_expected_result(wat);
+        let expected = binding.as_str();
+
+        let shared_flags = settings::Flags::new(settings::builder());
+        let isa_builder = lookup(triple).unwrap();
+        let isa = isa_builder.build(shared_flags).unwrap();
+
+        let mut validator = Validator::new();
+        let parser = WasmParser::new(0);
+        let mut types = Default::default();
+        let tunables = Tunables::default();
+        let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
+            .translate(parser, &wasm)
+            .context("Failed to translate WebAssembly module")
+            .unwrap();
+        let _ = types.finish();
+
+        let body_inputs = std::mem::take(&mut translation.function_body_inputs);
+        let module = &translation.module;
+        let types = translation.get_types();
+
+        let binding = body_inputs
+            .into_iter()
+            .flat_map(|func| compile(&*isa, module, types, func))
+            .collect::<Vec<String>>()
+            .join("\n");
+        let actual = binding.as_str();
+
+        if std::env::var("WINCH_TEST_BLESS").unwrap_or_default() == "1" {
+            let new_wat = rewrite_expected(wat, actual);
+
+            std::fs::write(test_path, new_wat)
+                .with_context(|| format!("failed to write file: {}", test_path))
+                .unwrap();
+
+            return;
+        }
+
+        if expected.trim() != actual.trim() {
+            eprintln!(
+                "\n{}",
+                TextDiff::from_lines(expected, actual)
+                    .unified_diff()
+                    .header("expected", "actual")
+            );
+
+            eprintln!(
+                "note: You can re-run with the `WINCH_TEST_BLESS=1` environment variable set to update test expectations.\n"
+            );
+
+            panic!("Did not get the expected translation");
+        }
+    }
+
+    fn compile(
+        isa: &dyn TargetIsa,
+        module: &Module,
+        types: &Types,
+        f: (DefinedFuncIndex, FunctionBodyData<'_>),
+    ) -> Vec<String> {
+        let index = module.func_index(f.0);
+        let sig = types
+            .func_type_at(index.as_u32())
+            .expect(&format!("function type at index {:?}", index.as_u32()));
+        let FunctionBodyData { body, validator } = f.1;
+        let validator = validator.into_validator(Default::default());
+
+        let buffer = isa
+            .compile_function(&sig, &body, validator)
+            .expect("Couldn't compile function");
+
+        disasm(buffer.data(), isa).unwrap()
+    }
+}
diff --git a/winch/src/filetests.rs b/winch/src/filetests.rs
new file mode 100644
index 000000000..6917736c9
--- /dev/null
+++ b/winch/src/filetests.rs
@@ -0,0 +1,25 @@
+use std::process::Command;
+
+use anyhow::Result;
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+pub struct Options {
+    /// Passes extra arguments to `cargo test --package winch-filetests`. For example, to run a single
+    /// test, use `-- --test-threads 1 --test single_test_name`.
+    #[clap(last = true, value_parser)]
+    cargo_test_args: Vec<String>,
+}
+
+pub fn run(opts: &Options) -> Result<()> {
+    Command::new("cargo")
+        .arg("test")
+        .arg("--package")
+        .arg("winch-filetests")
+        .arg("--")
+        .args(&opts.cargo_test_args)
+        .spawn()?
+        .wait()
+        .map(|_| ())
+        .map_err(|e| anyhow::anyhow!("Failed to run cargo test: {}", e))
+}
diff --git a/winch/test-macros/Cargo.toml b/winch/test-macros/Cargo.toml
new file mode 100644
index 000000000..3c69e9373
--- /dev/null
+++ b/winch/test-macros/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+authors = ["The Winch Project Developers"]
+name = "winch-test-macros"
+description = "Winch test macros"
+license = "Apache-2.0 WITH LLVM-exception"
+repository = "https://github.com/bytecodealliance/wasmtime"
+version = "0.0.0"
+publish = false
+edition.workspace = true
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0"
+syn = { version = "1.0", features = ["full"]}
+proc-macro2 = "1.0"
+glob = { workspace = true }
diff --git a/winch/test-macros/src/lib.rs b/winch/test-macros/src/lib.rs
new file mode 100644
index 000000000..e0d34506b
--- /dev/null
+++ b/winch/test-macros/src/lib.rs
@@ -0,0 +1,82 @@
+extern crate proc_macro;
+
+use std::path::Path;
+
+use glob::glob;
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::ItemFn;
+
+fn get_test_name_for_root(root: &Path, path: &Path) -> String {
+    let test_name = path
+        .strip_prefix(root)
+        .unwrap()
+        .to_str()
+        .unwrap()
+        .replace("/", "_")
+        .replace("\\", "_")
+        .replace(".wat", "");
+
+    format!("winch_filetests_{}", test_name)
+}
+
+/// Generate a test case for every .wat file in the filetests directory.
+/// This should only be used from the filetests crate.
+#[proc_macro_attribute]
+pub fn generate_file_tests(_attr: TokenStream, input: TokenStream) -> TokenStream {
+    // Parse the input as a function.
+    let input = proc_macro2::TokenStream::from(input);
+
+    let fn_ast: ItemFn =
+        syn::parse(input.clone().into()).expect("Failed to parse tokens as function");
+
+    // Get the function's name and body.
+    let name = &fn_ast.sig.ident;
+
+    let filetests_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../filetests/filetests");
+
+    let test_file_entries = glob(format!("{}/**/*.wat", filetests_dir.to_str().unwrap()).as_str())
+        .expect("Failed to read glob pattern");
+
+    // Create a list of test cases by opening every .wat file in the directory.
+    let test_cases = test_file_entries.map(|entry| {
+        let path = entry.expect("Failed to read glob entry");
+
+        let full = path.to_str().expect("Path for file was empty");
+
+        let test_name = proc_macro2::Ident::new(
+            &get_test_name_for_root(&filetests_dir, &path),
+            proc_macro2::Span::call_site(),
+        );
+        quote! {
+            #[test]
+            fn #test_name() {
+                #name(#full);
+            }
+        }
+    });
+
+    // Assemble the output by combining the function and test cases.
+    let output = quote! {
+        #input
+
+        #(#test_cases)*
+    };
+
+    output.into()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_get_test_name_for_root_unix() {
+        let root = Path::new("/home/user/Documents/winch/filetests/filetests");
+        let path = Path::new("/home/user/Documents/winch/filetests/filetests/simd/simple.wat");
+
+        let test_name = get_test_name_for_root(root, path);
+
+        assert_eq!(test_name, "winch_filetests_simd_simple");
+    }
+}