Repository: bytecodealliance/wasmtime
Pull request #5471 (2022-12-19 19:47:26)

wiggle: copy guest slices back to shared memory
This change upgrades `UnsafeGuestSlice` in Wiggle to expose more
functionality to be able to use `std::ptr::copy` for writing bytes into
Wasm shared memory. Additionally, it adds a new `GuestCow` type for
delineating between Wasm memory regions that can be borrowed (non-shared
memory) or must be copied (shared memory) in order to maintain Rust
guarantees.

With these in place, it is now possible to implement the `preview1`
"read" functions for shared memory. Previously, these would panic if
attempting to copy to a shared memory. This change removes the panic and
introduces some (rather complex) logic for handling both the shared and
non-shared cases:
- if reading into a Wasm non-shared memory, Wiggle guarantees that no
  other guest pointers will touch the memory region and, in the absence
  of concurrency, a WASI function can write directly to this memory
- if reading into a Wasm shared memory, the memory region can be
  concurrently modified. At @alexcrichton's request re: Rust safety,
  this change copies all of the bytes into an intermediate buffer before
  using `std::ptr::copy` to move them into Wasm memory.

This change only applies to the `preview0` and `preview1`
implementations of `wasi-common`. Fixing up other WASI implementations
(esp. wasi-crypto) is left for later.


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 86ef20f56..bd93d98ce 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -172,6 +172,11 @@ jobs:
       env:
         CARGO_PROFILE_DEV_DEBUG_ASSERTIONS: false
 
+    # Check whether `wasmtime` cross-compiles to x86_64-unknown-freebsd
+    # TODO: We aren't building with default features since the `ittapi` crate fails to compile on freebsd.
+    - run: rustup target add x86_64-unknown-freebsd
+    - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache --target x86_64-unknown-freebsd
+
   # Check whether `wasmtime` cross-compiles to aarch64-pc-windows-msvc
   # We don't build nor test it because it lacks trap handling.
   # Tracking issue: https://github.com/bytecodealliance/wasmtime/issues/4992
diff --git a/README.md b/README.md
index 654b73e71..3f7c30213 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,7 @@ the implementation:
 * **[Python]** - the [`wasmtime` PyPI package]
 * **[.NET]** - the [`Wasmtime` NuGet package]
 * **[Go]** - the [`wasmtime-go` repository]
+* **[Ruby]** - the [`wasmtime` gem]
 
 [Rust]: https://bytecodealliance.github.io/wasmtime/lang-rust.html
 [C]: https://bytecodealliance.github.io/wasmtime/examples-c-embed.html
@@ -130,6 +131,8 @@ the implementation:
 [wasmtime-cpp]: https://github.com/bytecodealliance/wasmtime-cpp
 [`wasmtime` Conan package]: https://conan.io/center/wasmtime
 [`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
 
 ## Documentation
 
diff --git a/RELEASES.md b/RELEASES.md
index 959d07312..d7f79c1a3 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -12,7 +12,7 @@ Unreleased.
 
 ## 4.0.0
 
-Unreleased.
+Released 2022-12-20
 
 ### Added
 
diff --git a/cranelift/codegen/src/isa/aarch64/inst/mod.rs b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
index 518de54d2..06c1f26d8 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/mod.rs
@@ -2657,7 +2657,16 @@ impl Inst {
                 }
                 s
             }
-            &Inst::Ret { .. } => "ret".to_string(),
+            &Inst::Ret { ref rets } => {
+                let mut s = "ret".to_string();
+                for ret in rets {
+                    use std::fmt::Write;
+                    let preg = pretty_print_reg(ret.preg, &mut empty_allocs);
+                    let vreg = pretty_print_reg(ret.vreg, allocs);
+                    write!(&mut s, " {}={}", vreg, preg).unwrap();
+                }
+                s
+            }
             &Inst::AuthenticatedRet { key, is_hint, .. } => {
                 let key = match key {
                     APIKey::A => "a",
diff --git a/cranelift/codegen/src/isa/aarch64/lower.isle b/cranelift/codegen/src/isa/aarch64/lower.isle
index b435a36eb..388758c01 100644
--- a/cranelift/codegen/src/isa/aarch64/lower.isle
+++ b/cranelift/codegen/src/isa/aarch64/lower.isle
@@ -887,6 +887,36 @@
 
 ;;; Rules for integer min/max: umin, smin, umax, smax ;;;;;;;;;;;;;;;;;;;;;;;;;
 
+;; `i64` and smaller.
+
+;; cmp     $x, $y
+;; csel    .., $x, $y, $cc
+(decl cmp_and_choose (Type Cond bool Value Value) ValueRegs)
+(rule (cmp_and_choose (fits_in_64 ty) cc _ x y)
+      (let ((x Reg (put_in_reg x))
+            (y Reg (put_in_reg y)))
+       (with_flags_reg (cmp (operand_size ty) x y)
+                       (csel cc x y))))
+
+;; `i16` and `i8` min/max require sign extension as
+;; the comparison operates on (at least) 32 bits.
+(rule 1 (cmp_and_choose (fits_in_16 ty) cc signed x y)
+      (let ((x Reg (extend (put_in_reg x) signed (ty_bits ty) 32))
+            (y Reg (extend (put_in_reg y) signed (ty_bits ty) 32)))
+      (with_flags_reg (cmp (operand_size ty) x y)
+                      (csel cc x y))))
+
+(rule 2 (lower (has_type (and (fits_in_64 ty) (ty_int _)) (umin x y)))
+      (cmp_and_choose ty (Cond.Lo) $false x y))
+(rule 2 (lower (has_type (and (fits_in_64 ty) (ty_int _)) (smin x y)))
+      (cmp_and_choose ty (Cond.Lt) $true x y))
+(rule 2 (lower (has_type (and (fits_in_64 ty) (ty_int _)) (umax x y)))
+      (cmp_and_choose ty (Cond.Hi) $false x y))
+(rule 2 (lower (has_type (and (fits_in_64 ty) (ty_int _)) (smax x y)))
+      (cmp_and_choose ty (Cond.Gt) $true x y))
+
+;; Vector types.
+
 (rule (lower (has_type ty @ (not_i64x2) (smin x y)))
       (vec_rrr (VecALUOp.Smin) x y (vector_size ty)))
 
@@ -1706,9 +1736,9 @@
 ;;;; Rules for `select` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (rule (lower (has_type ty
-                       (select (icmp cc
-                                     x @ (value_type in_ty)
-                                     y)
+                       (select (maybe_uextend (icmp cc
+                                                    x @ (value_type in_ty)
+                                                    y))
                                rn
                                rm)))
       (let ((comparison FlagsAndCC (lower_icmp_into_flags cc x y in_ty)))
@@ -1719,7 +1749,7 @@
                      rm)))
 
 (rule (lower (has_type ty
-                       (select (fcmp cc x @ (value_type in_ty) y)
+                       (select (maybe_uextend (fcmp cc x @ (value_type in_ty) y))
                                rn
                                rm)))
       (let ((cond Cond (fp_cond_code cc)))
@@ -1757,7 +1787,7 @@
 ;;;; Rules for `select_spectre_guard` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (rule (lower (has_type ty
-                       (select_spectre_guard (icmp cc x @ (value_type in_ty) y)
+                       (select_spectre_guard (maybe_uextend (icmp cc x @ (value_type in_ty) y))
                                              if_true
                                              if_false)))
       (let ((comparison FlagsAndCC (lower_icmp_into_flags cc x y in_ty))
@@ -2434,7 +2464,7 @@
 ;;; Rules for `brz`/`brnz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;; `brz` following `icmp`
-(rule (lower_branch (brz (icmp cc x @ (value_type ty) y) _ _) targets)
+(rule (lower_branch (brz (maybe_uextend (icmp cc x @ (value_type ty) y)) _ _) targets)
       (let ((comparison FlagsAndCC (lower_icmp_into_flags cc x y ty))
             ;; Negate the condition for `brz`.
             (cond Cond (invert_cond (cond_code (flags_and_cc_cc comparison))))
@@ -2446,7 +2476,7 @@
                                           not_taken
                                           (cond_br_cond cond))))))
 ;; `brnz` following `icmp`
-(rule (lower_branch (brnz (icmp cc x @ (value_type ty) y) _ _) targets)
+(rule (lower_branch (brnz (maybe_uextend (icmp cc x @ (value_type ty) y)) _ _) targets)
       (let ((comparison FlagsAndCC (lower_icmp_into_flags cc x y ty))
             (cond Cond (cond_code (flags_and_cc_cc comparison)))
             (taken BranchTarget (branch_target targets 0))
@@ -2457,7 +2487,7 @@
                                           not_taken
                                           (cond_br_cond cond))))))
 ;; `brz` following `fcmp`
-(rule (lower_branch (brz (fcmp cc x @ (value_type (ty_scalar_float ty)) y) _ _) targets)
+(rule (lower_branch (brz (maybe_uextend (fcmp cc x @ (value_type (ty_scalar_float ty)) y)) _ _) targets)
       (let ((cond Cond (fp_cond_code cc))
             (cond Cond (invert_cond cond)) ;; negate for `brz`
             (taken BranchTarget (branch_target targets 0))
@@ -2467,7 +2497,7 @@
                                 (cond_br taken not_taken
                                  (cond_br_cond cond))))))
 ;; `brnz` following `fcmp`
-(rule (lower_branch (brnz (fcmp cc x @ (value_type (ty_scalar_float ty)) y) _ _) targets)
+(rule (lower_branch (brnz (maybe_uextend (fcmp cc x @ (value_type (ty_scalar_float ty)) y)) _ _) targets)
       (let ((cond Cond (fp_cond_code cc))
             (taken BranchTarget (branch_target targets 0))
             (not_taken BranchTarget (branch_target targets 1)))
diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle
index acaf90f23..9d4042b22 100644
--- a/cranelift/codegen/src/isa/riscv64/inst.isle
+++ b/cranelift/codegen/src/isa/riscv64/inst.isle
@@ -148,6 +148,13 @@
       (rm Reg)
       (ty Type))
 
+    ;; A MOV instruction, but where the source register is a non-allocatable
+    ;; PReg. It's important that the register be non-allocatable, as regalloc2
+    ;; will not see it as used.
+    (MovFromPReg
+      (rd WritableReg)
+      (rm PReg))
+
     (Fence
       (pred FenceReq)
       (succ FenceReq))
@@ -1958,9 +1965,6 @@
   (lower_branch (br_table index _ _) targets)
   (lower_br_table index targets))
 
-(decl x_reg (u8) Reg)
-(extern constructor x_reg x_reg)
-
 (decl load_ra () Reg)
 (extern constructor load_ra load_ra)
 
@@ -2142,3 +2146,20 @@
   (lower_bmask $I128 $I128 val)
   (let ((res ValueRegs (lower_bmask $I64 $I128 val)))
     (value_regs (value_regs_get res 0) (value_regs_get res 0))))
+
+
+;;;; Helpers for physical registers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(decl gen_mov_from_preg (PReg) Reg)
+
+(rule
+  (gen_mov_from_preg rm)
+  (let ((rd WritableReg (temp_writable_reg $I64))
+        (_ Unit (emit (MInst.MovFromPReg rd rm))))
+    rd))
+
+(decl fp_reg () PReg)
+(extern constructor fp_reg fp_reg)
+
+(decl sp_reg () PReg)
+(extern constructor sp_reg sp_reg)
diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
index fb7295d1b..49cf20878 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
@@ -1123,6 +1123,19 @@ impl MachInstEmit for Inst {
                     }
                 }
             }
+
+            &Inst::MovFromPReg { rd, rm } => {
+                debug_assert!([px_reg(2), px_reg(8)].contains(&rm));
+                let rd = allocs.next_writable(rd);
+                let x = Inst::AluRRImm12 {
+                    alu_op: AluOPRRI::Ori,
+                    rd,
+                    rs: Reg::from(rm),
+                    imm12: Imm12::zero(),
+                };
+                x.emit(&[], sink, emit_info, state);
+            }
+
             &Inst::BrTable {
                 index,
                 tmp1,
@@ -1130,86 +1143,128 @@ impl MachInstEmit for Inst {
             } => {
                 let index = allocs.next(index);
                 let tmp1 = allocs.next_writable(tmp1);
+                let tmp2 = writable_spilltmp_reg();
 
-                Inst::load_constant_u32(writable_spilltmp_reg(), targets.len() as u64, &mut |_| {
-                    writable_spilltmp_reg()
-                })
-                .iter()
-                .for_each(|i| i.emit(&[], sink, emit_info, state));
+                // The default target is passed in as the 0th element of `targets`
+                // separate it here for clarity.
+                let default_target = targets[0];
+                let targets = &targets[1..];
+
+                // We emit a bounds check on the index, if the index is larger than the number of
+                // jump table entries, we jump to the default block.  Otherwise we compute a jump
+                // offset by multiplying the index by 8 (the size of each entry) and then jump to
+                // that offset. Each jump table entry is a regular auipc+jalr which we emit sequentially.
+                //
+                // Build the following sequence:
+                // bounds_check:
+                //     li      tmp, n_labels
+                //     bltu    index, tmp, compute_target
+                // jump_to_default_block:
+                //     auipc   pc, 0
+                //     jalr    zero, pc, default_block
+                // compute_target:
+                //     auipc   pc, 0
+                //     slli    tmp, index, 3
+                //     add     pc, pc, tmp
+                //     jalr    zero, pc, 0x10
+                // jump_table:
+                //     ; This repeats for each entry in the jumptable
+                //     auipc   pc, 0
+                //     jalr    zero, pc, block_target
+
+                // Bounds check.
+                //
+                // Check if the index passed in is larger than the number of jumptable
+                // entries that we have. If it is, we fallthrough to a jump into the
+                // default block.
+                Inst::load_constant_u32(tmp2, targets.len() as u64, &mut |_| tmp2)
+                    .iter()
+                    .for_each(|i| i.emit(&[], sink, emit_info, state));
                 Inst::CondBr {
                     taken: BranchTarget::offset(Inst::INSTRUCTION_SIZE * 3),
                     not_taken: BranchTarget::zero(),
                     kind: IntegerCompare {
                         kind: IntCC::UnsignedLessThan,
                         rs1: index,
-                        rs2: spilltmp_reg(),
+                        rs2: tmp2.to_reg(),
                     },
                 }
                 .emit(&[], sink, emit_info, state);
                 sink.use_label_at_offset(
                     sink.cur_offset(),
-                    targets[0].as_label().unwrap(),
+                    default_target.as_label().unwrap(),
                     LabelUse::PCRel32,
                 );
-                Inst::construct_auipc_and_jalr(None, writable_spilltmp_reg(), 0)
+                Inst::construct_auipc_and_jalr(None, tmp2, 0)
                     .iter()
                     .for_each(|i| i.emit(&[], sink, emit_info, state));
 
-                let mut insts = SmallInstVec::new();
-                // get current pc.
-                insts.push(Inst::Auipc {
+                // Compute the jump table offset.
+                // We need to emit a PC relative offset,
+
+                // Get the current PC.
+                Inst::Auipc {
                     rd: tmp1,
                     imm: Imm20::from_bits(0),
-                });
-                // t *= 8; very jump that I emit is 8 byte size.
-                insts.push(Inst::AluRRImm12 {
+                }
+                .emit(&[], sink, emit_info, state);
+
+                // Multiply the index by 8, since that is the size in
+                // bytes of each jump table entry
+                Inst::AluRRImm12 {
                     alu_op: AluOPRRI::Slli,
-                    rd: writable_spilltmp_reg(),
+                    rd: tmp2,
                     rs: index,
                     imm12: Imm12::from_bits(3),
-                });
-                // tmp1 += t
-                insts.push(Inst::AluRRR {
+                }
+                .emit(&[], sink, emit_info, state);
+
+                // Calculate the base of the jump, PC + the offset from above.
+                Inst::AluRRR {
                     alu_op: AluOPRRR::Add,
                     rd: tmp1,
                     rs1: tmp1.to_reg(),
-                    rs2: spilltmp_reg(),
-                });
-                insts.push(Inst::Jalr {
+                    rs2: tmp2.to_reg(),
+                }
+                .emit(&[], sink, emit_info, state);
+
+                // Jump to the middle of the jump table.
+                // We add a 16 byte offset here, since we used 4 instructions
+                // since the AUIPC that was used to get the PC.
+                Inst::Jalr {
                     rd: writable_zero_reg(),
                     base: tmp1.to_reg(),
-                    offset: Imm12::from_bits(16),
-                });
+                    offset: Imm12::from_bits((4 * Inst::INSTRUCTION_SIZE) as i16),
+                }
+                .emit(&[], sink, emit_info, state);
+
+                // Emit the jump table.
+                //
+                // Each entry is a aupc + jalr to the target block. We also start with a island
+                // if necessary.
 
-                // here is all the jumps.
-                let mut need_label_use = vec![];
-                for t in targets.iter().skip(1) {
-                    need_label_use.push((insts.len(), t.clone()));
-                    insts.extend(Inst::construct_auipc_and_jalr(
-                        None,
-                        writable_spilltmp_reg(),
-                        0,
-                    ));
-                }
-                // emit island if need.
-                let distance = (insts.len() * 4) as u32;
+                // Each entry in the jump table is 2 instructions, so 8 bytes. Check if
+                // we need to emit a jump table here to support that jump.
+                let distance = (targets.len() * 2 * Inst::INSTRUCTION_SIZE as usize) as u32;
                 if sink.island_needed(distance) {
                     sink.emit_island(distance);
                 }
-                let mut need_label_use = &need_label_use[..];
-                insts.into_iter().enumerate().for_each(|(index, inst)| {
-                    if !need_label_use.is_empty() && need_label_use[0].0 == index {
-                        sink.use_label_at_offset(
-                            sink.cur_offset(),
-                            need_label_use[0].1.as_label().unwrap(),
-                            LabelUse::PCRel32,
-                        );
-                        need_label_use = &need_label_use[1..];
-                    }
-                    inst.emit(&[], sink, emit_info, state);
-                });
-                // emit the island before, so we can safely
-                // disable the worst-case-size check in this case.
+
+                // Emit the jumps back to back
+                for target in targets.iter() {
+                    sink.use_label_at_offset(
+                        sink.cur_offset(),
+                        target.as_label().unwrap(),
+                        LabelUse::PCRel32,
+                    );
+
+                    Inst::construct_auipc_and_jalr(None, tmp2, 0)
+                        .iter()
+                        .for_each(|i| i.emit(&[], sink, emit_info, state));
+                }
+
+                // We've just emitted an island that is safe up to *here*.
+                // Mark it as such so that we don't needlessly emit additional islands.
                 start_off = sink.cur_offset();
             }
 
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 5022e8d31..956909df3 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -431,6 +431,10 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
             collector.reg_use(rm);
             collector.reg_def(rd);
         }
+        &Inst::MovFromPReg { rd, rm } => {
+            debug_assert!([px_reg(2), px_reg(8)].contains(&rm));
+            collector.reg_def(rd);
+        }
         &Inst::Fence { .. } => {}
         &Inst::FenceI => {}
         &Inst::ECall => {}
@@ -1402,8 +1406,16 @@ impl Inst {
                 }
                 s
             }
-            &Inst::Ret { .. } => {
-                format!("ret")
+            &Inst::Ret { ref rets } => {
+                let mut s = "ret".to_string();
+                let mut empty_allocs = AllocationConsumer::default();
+                for ret in rets {
+                    use std::fmt::Write;
+                    let preg = format_reg(ret.preg, &mut empty_allocs);
+                    let vreg = format_reg(ret.vreg, allocs);
+                    write!(&mut s, " {}={}", vreg, preg).unwrap();
+                }
+                s
             }
 
             &MInst::Extend {
@@ -1530,6 +1542,12 @@ impl Inst {
                 };
                 format!("{} {},{}", v, rd, rm)
             }
+            &MInst::MovFromPReg { rd, rm } => {
+                let rd = format_reg(rd.to_reg(), allocs);
+                debug_assert!([px_reg(2), px_reg(8)].contains(&rm));
+                let rm = reg_name(Reg::from(rm));
+                format!("mv {},{}", rd, rm)
+            }
             &MInst::Fence { pred, succ } => {
                 format!(
                     "fence {},{}",
diff --git a/cranelift/codegen/src/isa/riscv64/lower.isle b/cranelift/codegen/src/isa/riscv64/lower.isle
index 9209babc3..060b8a589 100644
--- a/cranelift/codegen/src/isa/riscv64/lower.isle
+++ b/cranelift/codegen/src/isa/riscv64/lower.isle
@@ -858,10 +858,10 @@
 ;;; Rules for `get_{frame,stack}_pointer` and `get_return_address` ;;;;;;;;;;;;;
 
 (rule (lower (get_frame_pointer))
-  (gen_move2 (x_reg 8) $I64 $I64))
+  (gen_mov_from_preg (fp_reg)))
 
 (rule (lower (get_stack_pointer))
-  (gen_move2 (x_reg 2) $I64 $I64))
+  (gen_mov_from_preg (sp_reg)))
 
 (rule (lower (get_return_address))
   (load_ra))
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index d008ff228..1454855ee 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -407,9 +407,15 @@ impl generated_code::Context for IsleContext<'_, '_, MInst, Riscv64Backend> {
             targets,
         });
     }
-    fn x_reg(&mut self, x: u8) -> Reg {
-        x_reg(x as usize)
+
+    fn fp_reg(&mut self) -> PReg {
+        px_reg(8)
+    }
+
+    fn sp_reg(&mut self) -> PReg {
+        px_reg(2)
     }
+
     fn shift_int_to_most_significant(&mut self, v: Reg, ty: Type) -> Reg {
         assert!(ty.is_int() && ty.bits() <= 64);
         if ty == I64 {
diff --git a/cranelift/codegen/src/isa/s390x/inst/mod.rs b/cranelift/codegen/src/isa/s390x/inst/mod.rs
index 88a40a928..3804a980c 100644
--- a/cranelift/codegen/src/isa/s390x/inst/mod.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/mod.rs
@@ -3145,9 +3145,16 @@ impl Inst {
                 }
                 s
             }
-            &Inst::Ret { link, .. } => {
+            &Inst::Ret { link, ref rets } => {
                 debug_assert_eq!(link, gpr(14));
-                format!("br {}", show_reg(link))
+                let mut s = format!("br {}", show_reg(link));
+                for ret in rets {
+                    use std::fmt::Write;
+                    let preg = pretty_print_reg(ret.preg, &mut empty_allocs);
+                    let vreg = pretty_print_reg(ret.vreg, allocs);
+                    write!(&mut s, " {}={}", vreg, preg).unwrap();
+                }
+                s
             }
             &Inst::Jump { dest } => {
                 let dest = dest.to_string();
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index 3a000b721..0d1bf0a0b 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -1537,7 +1537,16 @@ impl PrettyPrint for Inst {
                 s
             }
 
-            Inst::Ret { .. } => "ret".to_string(),
+            Inst::Ret { rets } => {
+                let mut s = "ret".to_string();
+                for ret in rets {
+                    use std::fmt::Write;
+                    let preg = regs::show_reg(ret.preg);
+                    let vreg = pretty_print_reg(ret.vreg, 8, allocs);
+                    write!(&mut s, " {}={}", vreg, preg).unwrap();
+                }
+                s
+            }
 
             Inst::JmpKnown { dst } => {
                 format!("{} {}", ljustify("jmp".to_string()), dst.to_string())
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 2e5adea32..bfe8003d6 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -1670,22 +1670,22 @@
 ;;  - `CC.BE -> C = 1 OR Z = 1` (below or equal)
 ;;  - `CC.NBE -> C = 0 AND Z = 0` (not below or equal)
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.Ordered) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.Ordered) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.NP) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.Unordered) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.Unordered) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.P) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.GreaterThan) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.GreaterThan) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.NBE) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.GreaterThanOrEqual) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.GreaterThanOrEqual) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.NB) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.UnorderedOrLessThan) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.UnorderedOrLessThan) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.B) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.UnorderedOrLessThanOrEqual) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.UnorderedOrLessThanOrEqual) a b)) x y)))
       (with_flags (x64_ucomis b a) (cmove_from_values ty (CC.BE) x y)))
 
 ;; Certain FloatCC variants are implemented by flipping the operands of the
@@ -1699,16 +1699,16 @@
 ;; not `LT | UNO`. By flipping the operands AND inverting the comparison (e.g.,
 ;; to `CC.NBE`), we also avoid these unordered cases.
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.LessThan) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.LessThan) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_from_values ty (CC.NBE) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.LessThanOrEqual) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.LessThanOrEqual) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_from_values ty (CC.NB) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.UnorderedOrGreaterThan) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.UnorderedOrGreaterThan) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_from_values ty (CC.B) x y)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.UnorderedOrGreaterThanOrEqual) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.UnorderedOrGreaterThanOrEqual) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_from_values ty (CC.BE) x y)))
 
 ;; `FloatCC.Equal` and `FloatCC.NotEqual` can only be implemented with multiple
@@ -1724,10 +1724,10 @@
 ;; More details about the CLIF semantics for `fcmp` are available at
 ;; https://docs.rs/cranelift-codegen/latest/cranelift_codegen/ir/trait.InstBuilder.html#method.fcmp.
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.Equal) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.Equal) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_or_from_values ty (CC.NZ) (CC.P) y x)))
 
-(rule (lower (has_type ty (select (fcmp (FloatCC.NotEqual) a b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (fcmp (FloatCC.NotEqual) a b)) x y)))
       (with_flags (x64_ucomis a b) (cmove_or_from_values ty (CC.NZ) (CC.P) x y)))
 
 ;; We also can lower `select`s that depend on an `icmp` test, but more simply
@@ -1735,7 +1735,7 @@
 ;; instruction plus a `CMOV`; recall that `cmove_from_values` here may emit more
 ;; than one instruction for certain types (e.g., XMM-held, I128).
 
-(rule (lower (has_type ty (select (icmp cc a @ (value_type (fits_in_64 a_ty)) b) x y)))
+(rule (lower (has_type ty (select (maybe_uextend (icmp cc a @ (value_type (fits_in_64 a_ty)) b)) x y)))
       (let ((size OperandSize (raw_operand_size_of_type a_ty)))
            (with_flags (x64_cmp size b a) (cmove_from_values ty cc x y))))
 
@@ -2873,20 +2873,11 @@
 
 ;; Rules for `brz` and `brnz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(rule 2 (lower_branch (brz (icmp cc a b) _ _) (two_targets taken not_taken))
+(rule 2 (lower_branch (brz (maybe_uextend (icmp cc a b)) _ _) (two_targets taken not_taken))
       (let ((cmp IcmpCondResult (invert_icmp_cond_result (emit_cmp cc a b))))
         (emit_side_effect (jmp_cond_icmp cmp taken not_taken))))
 
-(rule 2 (lower_branch (brz (uextend (icmp cc a b)) _ _) (two_targets taken not_taken))
-      (let ((cmp IcmpCondResult (invert_icmp_cond_result (emit_cmp cc a b))))
-        (emit_side_effect (jmp_cond_icmp cmp taken not_taken))))
-
-
-(rule 2 (lower_branch (brz (fcmp cc a b) _ _) (two_targets taken not_taken))
-      (let ((cmp FcmpCondResult (emit_fcmp (floatcc_inverse cc) a b)))
-        (emit_side_effect (jmp_cond_fcmp cmp taken not_taken))))
-
-(rule 2 (lower_branch (brz (uextend (fcmp cc a b)) _ _) (two_targets taken not_taken))
+(rule 2 (lower_branch (brz (maybe_uextend (fcmp cc a b)) _ _) (two_targets taken not_taken))
       (let ((cmp FcmpCondResult (emit_fcmp (floatcc_inverse cc) a b)))
         (emit_side_effect (jmp_cond_fcmp cmp taken not_taken))))
 
diff --git a/cranelift/codegen/src/machinst/isle.rs b/cranelift/codegen/src/machinst/isle.rs
index 57e3f18de..def41ade3 100644
--- a/cranelift/codegen/src/machinst/isle.rs
+++ b/cranelift/codegen/src/machinst/isle.rs
@@ -547,6 +547,21 @@ macro_rules! isle_lower_prelude_methods {
             self.lower_ctx.sink_inst(inst);
         }
 
+        #[inline]
+        fn maybe_uextend(&mut self, value: Value) -> Option<Value> {
+            if let Some(def_inst) = self.def_inst(value) {
+                if let InstructionData::Unary {
+                    opcode: Opcode::Uextend,
+                    arg,
+                } = self.lower_ctx.data(def_inst)
+                {
+                    return Some(*arg);
+                }
+            }
+
+            Some(value)
+        }
+
         #[inline]
         fn preg_to_reg(&mut self, preg: PReg) -> Reg {
             preg.into()
diff --git a/cranelift/codegen/src/machinst/vcode.rs b/cranelift/codegen/src/machinst/vcode.rs
index d0ad7806a..1e787ed30 100644
--- a/cranelift/codegen/src/machinst/vcode.rs
+++ b/cranelift/codegen/src/machinst/vcode.rs
@@ -568,11 +568,25 @@ impl<I: VCodeInst> VCodeBuilder<I> {
             }
 
             if let Some((dst, src)) = insn.is_move() {
+                // We should never see non-virtual registers present in move
+                // instructions.
+                assert!(
+                    src.is_virtual(),
+                    "the real register {:?} was used as the source of a move instruction",
+                    src
+                );
+                assert!(
+                    dst.to_reg().is_virtual(),
+                    "the real register {:?} was used as the destination of a move instruction",
+                    dst.to_reg()
+                );
+
                 let src = Operand::reg_use(Self::resolve_vreg_alias_impl(vreg_aliases, src.into()));
                 let dst = Operand::reg_def(Self::resolve_vreg_alias_impl(
                     vreg_aliases,
                     dst.to_reg().into(),
                 ));
+
                 // Note that regalloc2 requires these in (src, dst) order.
                 self.vcode.is_move.insert(InsnIndex::new(i), (src, dst));
             }
diff --git a/cranelift/codegen/src/prelude_lower.isle b/cranelift/codegen/src/prelude_lower.isle
index 330eb2476..d628ff6ec 100644
--- a/cranelift/codegen/src/prelude_lower.isle
+++ b/cranelift/codegen/src/prelude_lower.isle
@@ -234,6 +234,11 @@
 (decl pure partial is_sinkable_inst (Value) Inst)
 (extern constructor is_sinkable_inst is_sinkable_inst)
 
+;; Match a uextend or any other instruction, "seeing through" the uextend if
+;; present.
+(decl maybe_uextend (Value) Value)
+(extern extractor maybe_uextend maybe_uextend)
+
 ;; Instruction creation helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;; Emit an instruction.
diff --git a/cranelift/docs/index.md b/cranelift/docs/index.md
index 96e733fe7..735403b92 100644
--- a/cranelift/docs/index.md
+++ b/cranelift/docs/index.md
@@ -47,7 +47,7 @@
  - [cranelift-object](https://docs.rs/cranelift-object)
     This crate provides a object-based backend for `cranelift-module`, which
     emits native object files using the
-    `object <https://github.com/gimli-rs/object>`_ library.
+    [object](https://github.com/gimli-rs/object) library.
 
  - [cranelift-jit](https://docs.rs/cranelift-jit)
     This crate provides a JIT backend for `cranelift-module`, which
diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs
index ebdd89a5c..fd002835c 100644
--- a/cranelift/fuzzgen/src/function_generator.rs
+++ b/cranelift/fuzzgen/src/function_generator.rs
@@ -188,6 +188,26 @@ fn insert_const(
     Ok(())
 }
 
+fn insert_bitcast(
+    fgen: &mut FunctionGenerator,
+    builder: &mut FunctionBuilder,
+    _opcode: Opcode,
+    args: &'static [Type],
+    rets: &'static [Type],
+) -> Result<()> {
+    let from_var = fgen.get_variable_of_type(args[0])?;
+    let from_val = builder.use_var(from_var);
+
+    let to_var = fgen.get_variable_of_type(rets[0])?;
+
+    // TODO: We can generate little/big endian flags here.
+    let memflags = MemFlags::new();
+
+    let res = builder.ins().bitcast(rets[0], memflags, from_val);
+    builder.def_var(to_var, res);
+    Ok(())
+}
+
 fn insert_load_store(
     fgen: &mut FunctionGenerator,
     builder: &mut FunctionBuilder,
@@ -252,9 +272,7 @@ const OPCODE_SIGNATURES: &'static [(
     (Opcode::IaddCout, &[I8, I8], &[I8, I8], insert_opcode),
     #[cfg(not(target_arch = "x86_64"))]
     (Opcode::IaddCout, &[I16, I16], &[I16, I8], insert_opcode),
-    #[cfg(not(target_arch = "x86_64"))]
     (Opcode::IaddCout, &[I32, I32], &[I32, I8], insert_opcode),
-    #[cfg(not(target_arch = "x86_64"))]
     (Opcode::IaddCout, &[I64, I64], &[I64, I8], insert_opcode),
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::IaddCout, &[I128, I128], &[I128, I8], insert_opcode),
@@ -347,58 +365,42 @@ const OPCODE_SIGNATURES: &'static [(
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::Iabs, &[I128], &[I128], insert_opcode),
     // Smin
-    // smin not implemented in some backends:
+    // smin.i128 is not implemented in some backends:
     //   x64: https://github.com/bytecodealliance/wasmtime/issues/3370
     //   aarch64: https://github.com/bytecodealliance/wasmtime/issues/4313
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smin, &[I8, I8], &[I8], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smin, &[I16, I16], &[I16], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smin, &[I32, I32], &[I32], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smin, &[I64, I64], &[I64], insert_opcode),
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::Smin, &[I128, I128], &[I128], insert_opcode),
     // Umin
-    // umin not implemented in some backends:
+    // umin.i128 is not implemented in some backends:
     //   x64: https://github.com/bytecodealliance/wasmtime/issues/3370
     //   aarch64: https://github.com/bytecodealliance/wasmtime/issues/4313
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umin, &[I8, I8], &[I8], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umin, &[I16, I16], &[I16], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umin, &[I32, I32], &[I32], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umin, &[I64, I64], &[I64], insert_opcode),
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::Umin, &[I128, I128], &[I128], insert_opcode),
     // Smax
-    // smax not implemented in some backends:
+    // smax.i128 is not implemented in some backends:
     //   x64: https://github.com/bytecodealliance/wasmtime/issues/3370
     //   aarch64: https://github.com/bytecodealliance/wasmtime/issues/4313
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smax, &[I8, I8], &[I8], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smax, &[I16, I16], &[I16], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smax, &[I32, I32], &[I32], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Smax, &[I64, I64], &[I64], insert_opcode),
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::Smax, &[I128, I128], &[I128], insert_opcode),
     // Umax
-    // umax not implemented in some backends:
+    // umax.i128 is not implemented in some backends:
     //   x64: https://github.com/bytecodealliance/wasmtime/issues/3370
     //   aarch64: https://github.com/bytecodealliance/wasmtime/issues/4313
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umax, &[I8, I8], &[I8], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umax, &[I16, I16], &[I16], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umax, &[I32, I32], &[I32], insert_opcode),
-    #[cfg(not(target_arch = "aarch64"))]
     (Opcode::Umax, &[I64, I64], &[I64], insert_opcode),
     #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
     (Opcode::Umax, &[I128, I128], &[I128], insert_opcode),
@@ -1039,6 +1041,11 @@ const OPCODE_SIGNATURES: &'static [(
     (Opcode::Istore16, &[I32], &[], insert_load_store),
     (Opcode::Istore16, &[I64], &[], insert_load_store),
     (Opcode::Istore32, &[I64], &[], insert_load_store),
+    // Bitcast
+    (Opcode::Bitcast, &[F32], &[I32], insert_bitcast),
+    (Opcode::Bitcast, &[I32], &[F32], insert_bitcast),
+    (Opcode::Bitcast, &[F64], &[I64], insert_bitcast),
+    (Opcode::Bitcast, &[I64], &[F64], insert_bitcast),
     // Integer Consts
     (Opcode::Iconst, &[], &[I8], insert_const),
     (Opcode::Iconst, &[], &[I16], insert_const),
diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs
index a155bad76..0295777da 100644
--- a/crates/wasi-common/src/snapshots/preview_0.rs
+++ b/crates/wasi-common/src/snapshots/preview_0.rs
@@ -5,6 +5,7 @@ use crate::sched::{
 };
 use crate::snapshots::preview_1::types as snapshot1_types;
 use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1;
+use crate::snapshots::preview_1::MAX_SHARED_BUFFER_SIZE;
 use crate::{ErrorExt, WasiCtx};
 use cap_std::time::Duration;
 use std::collections::HashSet;
@@ -532,23 +533,69 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::READ)?;
 
-        let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> =
-            iovs.iter()
-                .map(|iov_ptr| {
-                    let iov_ptr = iov_ptr?;
-                    let iov: types::Iovec = iov_ptr.read()?;
-                    Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?.expect(
-                        "cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)",
-                    ))
-                })
+        let iovs: Vec<wiggle::UnsafeGuestSlice<u8>> = iovs
+            .iter()
+            .map(|iov_ptr| {
+                let iov_ptr = iov_ptr?;
+                let iov: types::Iovec = iov_ptr.read()?;
+                Ok(iov.buf.as_array(iov.buf_len).as_unsafe_slice_mut()?)
+            })
+            .collect::<Result<_, Error>>()?;
+
+        // If the first iov structure is from shared memory we can safely assume
+        // all the rest will be. We then read into memory based on the memory's
+        // shared-ness:
+        // - if not shared, we copy directly into the Wasm memory
+        // - if shared, we use an intermediate buffer; this avoids Rust unsafety
+        //   due to holding on to a `&mut [u8]` of Wasm memory when we cannot
+        //   guarantee the `&mut` exclusivity--other threads could be modifying
+        //   the data as this functions writes to it. Though likely there is no
+        //   issue with OS writing to io structs in multi-threaded scenarios,
+        //   since we do not know here if `&dyn WasiFile` does anything else
+        //   (e.g., read), we cautiously incur some performance overhead by
+        //   copying twice.
+        let is_shared_memory = iovs
+            .iter()
+            .next()
+            .and_then(|s| Some(s.is_shared_memory()))
+            .unwrap_or(false);
+        let bytes_read: u64 = if is_shared_memory {
+            // Read into an intermediate buffer.
+            let total_available_size = iovs.iter().fold(0, |a, s| a + s.len());
+            let mut buffer = vec![0; total_available_size.min(MAX_SHARED_BUFFER_SIZE)];
+            let bytes_read = f.read_vectored(&mut [IoSliceMut::new(&mut buffer)]).await?;
+
+            // Copy the intermediate buffer into the Wasm shared memory--`iov`
+            // by `iov`.
+            let mut data_to_write = &buffer[0..];
+            for iov in iovs.into_iter() {
+                let len = data_to_write.len().min(iov.len());
+                iov.copy_from_slice(&data_to_write[0..len])?;
+                data_to_write = &data_to_write[len..];
+                if data_to_write.is_empty() {
+                    break;
+                }
+            }
+
+            bytes_read
+        } else {
+            // Convert all of the unsafe guest slices to safe ones--this uses
+            // Wiggle's internal borrow checker to ensure no overlaps. We assume
+            // here that, because the memory is not shared, there are no other
+            // threads to access it while it is written to.
+            let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
+                .into_iter()
+                .map(|iov| Ok(iov.as_slice_mut()?.unwrap()))
                 .collect::<Result<_, Error>>()?;
 
-        let mut ioslices: Vec<IoSliceMut> = guest_slices
-            .iter_mut()
-            .map(|s| IoSliceMut::new(&mut *s))
-            .collect();
+            // Read directly into the Wasm memory.
+            let mut ioslices: Vec<IoSliceMut> = guest_slices
+                .iter_mut()
+                .map(|s| IoSliceMut::new(&mut *s))
+                .collect();
+            f.read_vectored(&mut ioslices).await?
+        };
 
-        let bytes_read = f.read_vectored(&mut ioslices).await?;
         Ok(types::Size::try_from(bytes_read)?)
     }
 
@@ -563,23 +610,71 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::READ | FileCaps::SEEK)?;
 
-        let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> =
-            iovs.iter()
-                .map(|iov_ptr| {
-                    let iov_ptr = iov_ptr?;
-                    let iov: types::Iovec = iov_ptr.read()?;
-                    Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?.expect(
-                        "cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)",
-                    ))
-                })
+        let iovs: Vec<wiggle::UnsafeGuestSlice<u8>> = iovs
+            .iter()
+            .map(|iov_ptr| {
+                let iov_ptr = iov_ptr?;
+                let iov: types::Iovec = iov_ptr.read()?;
+                Ok(iov.buf.as_array(iov.buf_len).as_unsafe_slice_mut()?)
+            })
+            .collect::<Result<_, Error>>()?;
+
+        // If the first iov structure is from shared memory we can safely assume
+        // all the rest will be. We then read into memory based on the memory's
+        // shared-ness:
+        // - if not shared, we copy directly into the Wasm memory
+        // - if shared, we use an intermediate buffer; this avoids Rust unsafety
+        //   due to holding on to a `&mut [u8]` of Wasm memory when we cannot
+        //   guarantee the `&mut` exclusivity--other threads could be modifying
+        //   the data as this functions writes to it. Though likely there is no
+        //   issue with OS writing to io structs in multi-threaded scenarios,
+        //   since we do not know here if `&dyn WasiFile` does anything else
+        //   (e.g., read), we cautiously incur some performance overhead by
+        //   copying twice.
+        let is_shared_memory = iovs
+            .iter()
+            .next()
+            .and_then(|s| Some(s.is_shared_memory()))
+            .unwrap_or(false);
+        let bytes_read: u64 = if is_shared_memory {
+            // Read into an intermediate buffer.
+            let total_available_size = iovs.iter().fold(0, |a, s| a + s.len());
+            let mut buffer = vec![0; total_available_size.min(MAX_SHARED_BUFFER_SIZE)];
+            let bytes_read = f
+                .read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset)
+                .await?;
+
+            // Copy the intermediate buffer into the Wasm shared memory--`iov`
+            // by `iov`.
+            let mut data_to_write = &buffer[0..];
+            for iov in iovs.into_iter() {
+                let len = data_to_write.len().min(iov.len());
+                iov.copy_from_slice(&data_to_write[0..len])?;
+                data_to_write = &data_to_write[len..];
+                if data_to_write.is_empty() {
+                    break;
+                }
+            }
+
+            bytes_read
+        } else {
+            // Convert all of the unsafe guest slices to safe ones--this uses
+            // Wiggle's internal borrow checker to ensure no overlaps. We assume
+            // here that, because the memory is not shared, there are no other
+            // threads to access it while it is written to.
+            let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
+                .into_iter()
+                .map(|iov| Ok(iov.as_slice_mut()?.unwrap()))
                 .collect::<Result<_, Error>>()?;
 
-        let mut ioslices: Vec<IoSliceMut> = guest_slices
-            .iter_mut()
-            .map(|s| IoSliceMut::new(&mut *s))
-            .collect();
+            // Read directly into the Wasm memory.
+            let mut ioslices: Vec<IoSliceMut> = guest_slices
+                .iter_mut()
+                .map(|s| IoSliceMut::new(&mut *s))
+                .collect();
+            f.read_vectored_at(&mut ioslices, offset).await?
+        };
 
-        let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?;
         Ok(types::Size::try_from(bytes_read)?)
     }
 
@@ -593,16 +688,12 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::WRITE)?;
 
-        let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
+        let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
             .iter()
             .map(|iov_ptr| {
                 let iov_ptr = iov_ptr?;
                 let iov: types::Ciovec = iov_ptr.read()?;
-                Ok(iov
-                    .buf
-                    .as_array(iov.buf_len)
-                    .as_slice()?
-                    .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)"))
+                Ok(iov.buf.as_array(iov.buf_len).as_cow()?)
             })
             .collect::<Result<_, Error>>()?;
 
@@ -626,16 +717,12 @@ impl wasi_unstable::WasiUnstable for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::WRITE | FileCaps::SEEK)?;
 
-        let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
+        let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
             .iter()
             .map(|iov_ptr| {
                 let iov_ptr = iov_ptr?;
                 let iov: types::Ciovec = iov_ptr.read()?;
-                Ok(iov
-                    .buf
-                    .as_array(iov.buf_len)
-                    .as_slice()?
-                    .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)"))
+                Ok(iov.buf.as_array(iov.buf_len).as_cow()?)
             })
             .collect::<Result<_, Error>>()?;
 
diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs
index 37abf1f6f..c3cb00352 100644
--- a/crates/wasi-common/src/snapshots/preview_1.rs
+++ b/crates/wasi-common/src/snapshots/preview_1.rs
@@ -13,12 +13,16 @@ use crate::{
 use cap_std::time::{Duration, SystemClock};
 use std::convert::{TryFrom, TryInto};
 use std::io::{IoSlice, IoSliceMut};
-use std::ops::{Deref, DerefMut};
+use std::ops::Deref;
 use wiggle::GuestPtr;
 
 pub mod error;
 use error::{Error, ErrorExt};
 
+// Limit the size of intermediate buffers when copying to WebAssembly shared
+// memory.
+pub(crate) const MAX_SHARED_BUFFER_SIZE: usize = 1 << 16;
+
 wiggle::from_witx!({
     witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
     errors: { errno => trappable Error },
@@ -295,23 +299,69 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::READ)?;
 
-        let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> =
-            iovs.iter()
-                .map(|iov_ptr| {
-                    let iov_ptr = iov_ptr?;
-                    let iov: types::Iovec = iov_ptr.read()?;
-                    Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?.expect(
-                        "cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)",
-                    ))
-                })
+        let iovs: Vec<wiggle::UnsafeGuestSlice<u8>> = iovs
+            .iter()
+            .map(|iov_ptr| {
+                let iov_ptr = iov_ptr?;
+                let iov: types::Iovec = iov_ptr.read()?;
+                Ok(iov.buf.as_array(iov.buf_len).as_unsafe_slice_mut()?)
+            })
+            .collect::<Result<_, Error>>()?;
+
+        // If the first iov structure is from shared memory we can safely assume
+        // all the rest will be. We then read into memory based on the memory's
+        // shared-ness:
+        // - if not shared, we copy directly into the Wasm memory
+        // - if shared, we use an intermediate buffer; this avoids Rust unsafety
+        //   due to holding on to a `&mut [u8]` of Wasm memory when we cannot
+        //   guarantee the `&mut` exclusivity--other threads could be modifying
+        //   the data as this functions writes to it. Though likely there is no
+        //   issue with OS writing to io structs in multi-threaded scenarios,
+        //   since we do not know here if `&dyn WasiFile` does anything else
+        //   (e.g., read), we cautiously incur some performance overhead by
+        //   copying twice.
+        let is_shared_memory = iovs
+            .iter()
+            .next()
+            .and_then(|s| Some(s.is_shared_memory()))
+            .unwrap_or(false);
+        let bytes_read: u64 = if is_shared_memory {
+            // Read into an intermediate buffer.
+            let total_available_size = iovs.iter().fold(0, |a, s| a + s.len());
+            let mut buffer = vec![0; total_available_size.min(MAX_SHARED_BUFFER_SIZE)];
+            let bytes_read = f.read_vectored(&mut [IoSliceMut::new(&mut buffer)]).await?;
+
+            // Copy the intermediate buffer into the Wasm shared memory--`iov`
+            // by `iov`.
+            let mut data_to_write = &buffer[0..];
+            for iov in iovs.into_iter() {
+                let len = data_to_write.len().min(iov.len());
+                iov.copy_from_slice(&data_to_write[0..len])?;
+                data_to_write = &data_to_write[len..];
+                if data_to_write.is_empty() {
+                    break;
+                }
+            }
+
+            bytes_read
+        } else {
+            // Convert all of the unsafe guest slices to safe ones--this uses
+            // Wiggle's internal borrow checker to ensure no overlaps. We assume
+            // here that, because the memory is not shared, there are no other
+            // threads to access it while it is written to.
+            let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
+                .into_iter()
+                .map(|iov| Ok(iov.as_slice_mut()?.unwrap()))
                 .collect::<Result<_, Error>>()?;
 
-        let mut ioslices: Vec<IoSliceMut> = guest_slices
-            .iter_mut()
-            .map(|s| IoSliceMut::new(&mut *s))
-            .collect();
+            // Read directly into the Wasm memory.
+            let mut ioslices: Vec<IoSliceMut> = guest_slices
+                .iter_mut()
+                .map(|s| IoSliceMut::new(&mut *s))
+                .collect();
+            f.read_vectored(&mut ioslices).await?
+        };
 
-        let bytes_read = f.read_vectored(&mut ioslices).await?;
         Ok(types::Size::try_from(bytes_read)?)
     }
 
@@ -326,23 +376,71 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::READ | FileCaps::SEEK)?;
 
-        let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> =
-            iovs.iter()
-                .map(|iov_ptr| {
-                    let iov_ptr = iov_ptr?;
-                    let iov: types::Iovec = iov_ptr.read()?;
-                    Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?.expect(
-                        "cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)",
-                    ))
-                })
+        let iovs: Vec<wiggle::UnsafeGuestSlice<u8>> = iovs
+            .iter()
+            .map(|iov_ptr| {
+                let iov_ptr = iov_ptr?;
+                let iov: types::Iovec = iov_ptr.read()?;
+                Ok(iov.buf.as_array(iov.buf_len).as_unsafe_slice_mut()?)
+            })
+            .collect::<Result<_, Error>>()?;
+
+        // If the first iov structure is from shared memory we can safely assume
+        // all the rest will be. We then read into memory based on the memory's
+        // shared-ness:
+        // - if not shared, we copy directly into the Wasm memory
+        // - if shared, we use an intermediate buffer; this avoids Rust unsafety
+        //   due to holding on to a `&mut [u8]` of Wasm memory when we cannot
+        //   guarantee the `&mut` exclusivity--other threads could be modifying
+        //   the data as this functions writes to it. Though likely there is no
+        //   issue with OS writing to io structs in multi-threaded scenarios,
+        //   since we do not know here if `&dyn WasiFile` does anything else
+        //   (e.g., read), we cautiously incur some performance overhead by
+        //   copying twice.
+        let is_shared_memory = iovs
+            .iter()
+            .next()
+            .and_then(|s| Some(s.is_shared_memory()))
+            .unwrap_or(false);
+        let bytes_read: u64 = if is_shared_memory {
+            // Read into an intermediate buffer.
+            let total_available_size = iovs.iter().fold(0, |a, s| a + s.len());
+            let mut buffer = vec![0; total_available_size.min(MAX_SHARED_BUFFER_SIZE)];
+            let bytes_read = f
+                .read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset)
+                .await?;
+
+            // Copy the intermediate buffer into the Wasm shared memory--`iov`
+            // by `iov`.
+            let mut data_to_write = &buffer[0..];
+            for iov in iovs.into_iter() {
+                let len = data_to_write.len().min(iov.len());
+                iov.copy_from_slice(&data_to_write[0..len])?;
+                data_to_write = &data_to_write[len..];
+                if data_to_write.is_empty() {
+                    break;
+                }
+            }
+
+            bytes_read
+        } else {
+            // Convert all of the unsafe guest slices to safe ones--this uses
+            // Wiggle's internal borrow checker to ensure no overlaps. We assume
+            // here that, because the memory is not shared, there are no other
+            // threads to access it while it is written to.
+            let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
+                .into_iter()
+                .map(|iov| Ok(iov.as_slice_mut()?.unwrap()))
                 .collect::<Result<_, Error>>()?;
 
-        let mut ioslices: Vec<IoSliceMut> = guest_slices
-            .iter_mut()
-            .map(|s| IoSliceMut::new(&mut *s))
-            .collect();
+            // Read directly into the Wasm memory.
+            let mut ioslices: Vec<IoSliceMut> = guest_slices
+                .iter_mut()
+                .map(|s| IoSliceMut::new(&mut *s))
+                .collect();
+            f.read_vectored_at(&mut ioslices, offset).await?
+        };
 
-        let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?;
         Ok(types::Size::try_from(bytes_read)?)
     }
 
@@ -356,16 +454,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::WRITE)?;
 
-        let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
+        let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
             .iter()
             .map(|iov_ptr| {
                 let iov_ptr = iov_ptr?;
                 let iov: types::Ciovec = iov_ptr.read()?;
-                Ok(iov
-                    .buf
-                    .as_array(iov.buf_len)
-                    .as_slice()?
-                    .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)"))
+                Ok(iov.buf.as_array(iov.buf_len).as_cow()?)
             })
             .collect::<Result<_, Error>>()?;
 
@@ -389,16 +483,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::WRITE | FileCaps::SEEK)?;
 
-        let guest_slices: Vec<wiggle::GuestSlice<u8>> = ciovs
+        let guest_slices: Vec<wiggle::GuestCow<u8>> = ciovs
             .iter()
             .map(|iov_ptr| {
                 let iov_ptr = iov_ptr?;
                 let iov: types::Ciovec = iov_ptr.read()?;
-                Ok(iov
-                    .buf
-                    .as_array(iov.buf_len)
-                    .as_slice()?
-                    .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)"))
+                Ok(iov.buf.as_array(iov.buf_len).as_cow()?)
             })
             .collect::<Result<_, Error>>()?;
 
@@ -440,11 +530,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             if path_len < path_max_len as usize {
                 return Err(Error::name_too_long());
             }
-            let mut p_memory = path
-                .as_array(path_len as u32)
-                .as_slice_mut()?
-                .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)");
-            p_memory.copy_from_slice(path_bytes);
+            path.as_array(path_len as u32).copy_from_slice(path_bytes)?;
             Ok(())
         } else {
             Err(Error::not_supported())
@@ -737,11 +823,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
         if link_len > buf_len as usize {
             return Err(Error::range());
         }
-        let mut buf = buf
-            .as_array(link_len as u32)
-            .as_slice_mut()?
-            .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)");
-        buf.copy_from_slice(link_bytes);
+        buf.as_array(link_len as u32).copy_from_slice(link_bytes)?;
         Ok(link_len as types::Size)
     }
 
@@ -1029,11 +1111,30 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
         buf: &GuestPtr<'a, u8>,
         buf_len: types::Size,
     ) -> Result<(), Error> {
-        let mut buf = buf
-            .as_array(buf_len)
-            .as_slice_mut()?
-            .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)");
-        self.random.try_fill_bytes(buf.deref_mut())?;
+        let buf = buf.as_array(buf_len);
+        if buf.is_shared_memory() {
+            // If the Wasm memory is shared, copy to an intermediate buffer to
+            // avoid Rust unsafety (i.e., the called function could rely on
+            // `&mut [u8]`'s exclusive ownership which is not guaranteed due to
+            // potential access from other threads).
+            let mut copied: u32 = 0;
+            while copied < buf.len() {
+                let len = (buf.len() - copied).min(MAX_SHARED_BUFFER_SIZE as u32);
+                let mut tmp = vec![0; len as usize];
+                self.random.try_fill_bytes(&mut tmp)?;
+                let dest = buf
+                    .get_range(copied..copied + len)
+                    .unwrap()
+                    .as_unsafe_slice_mut()?;
+                dest.copy_from_slice(&tmp)?;
+                copied += len;
+            }
+        } else {
+            // If the Wasm memory is non-shared, copy directly into the linear
+            // memory.
+            let mem = &mut buf.as_slice_mut()?.unwrap();
+            self.random.try_fill_bytes(mem)?;
+        }
         Ok(())
     }
 
@@ -1069,25 +1170,72 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::READ)?;
 
-        let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> =
-            ri_data
-                .iter()
-                .map(|iov_ptr| {
-                    let iov_ptr = iov_ptr?;
-                    let iov: types::Iovec = iov_ptr.read()?;
-                    Ok(iov.buf.as_array(iov.buf_len).as_slice_mut()?.expect(
-                        "cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)",
-                    ))
-                })
+        let iovs: Vec<wiggle::UnsafeGuestSlice<u8>> = ri_data
+            .iter()
+            .map(|iov_ptr| {
+                let iov_ptr = iov_ptr?;
+                let iov: types::Iovec = iov_ptr.read()?;
+                Ok(iov.buf.as_array(iov.buf_len).as_unsafe_slice_mut()?)
+            })
+            .collect::<Result<_, Error>>()?;
+
+        // If the first iov structure is from shared memory we can safely assume
+        // all the rest will be. We then read into memory based on the memory's
+        // shared-ness:
+        // - if not shared, we copy directly into the Wasm memory
+        // - if shared, we use an intermediate buffer; this avoids Rust unsafety
+        //   due to holding on to a `&mut [u8]` of Wasm memory when we cannot
+        //   guarantee the `&mut` exclusivity--other threads could be modifying
+        //   the data as this functions writes to it. Though likely there is no
+        //   issue with OS writing to io structs in multi-threaded scenarios,
+        //   since we do not know here if `&dyn WasiFile` does anything else
+        //   (e.g., read), we cautiously incur some performance overhead by
+        //   copying twice.
+        let is_shared_memory = iovs
+            .iter()
+            .next()
+            .and_then(|s| Some(s.is_shared_memory()))
+            .unwrap_or(false);
+        let (bytes_read, ro_flags) = if is_shared_memory {
+            // Read into an intermediate buffer.
+            let total_available_size = iovs.iter().fold(0, |a, s| a + s.len());
+            let mut buffer = vec![0; total_available_size.min(MAX_SHARED_BUFFER_SIZE)];
+            let (bytes_read, ro_flags) = f
+                .sock_recv(&mut [IoSliceMut::new(&mut buffer)], RiFlags::from(ri_flags))
+                .await?;
+
+            // Copy the intermediate buffer into the Wasm shared memory--`iov`
+            // by `iov`.
+            let mut data_to_write = &buffer[0..];
+            for iov in iovs.into_iter() {
+                let len = data_to_write.len().min(iov.len());
+                iov.copy_from_slice(&data_to_write[0..len])?;
+                data_to_write = &data_to_write[len..];
+                if data_to_write.is_empty() {
+                    break;
+                }
+            }
+
+            (bytes_read, ro_flags)
+        } else {
+            // Convert all of the unsafe guest slices to safe ones--this uses
+            // Wiggle's internal borrow checker to ensure no overlaps. We assume
+            // here that, because the memory is not shared, there are no other
+            // threads to access it while it is written to.
+            let mut guest_slices: Vec<wiggle::GuestSliceMut<u8>> = iovs
+                .into_iter()
+                .map(|iov| Ok(iov.as_slice_mut()?.unwrap()))
                 .collect::<Result<_, Error>>()?;
 
-        let mut ioslices: Vec<IoSliceMut> = guest_slices
-            .iter_mut()
-            .map(|s| IoSliceMut::new(&mut *s))
-            .collect();
+            // Read directly into the Wasm memory.
+            let mut ioslices: Vec<IoSliceMut> = guest_slices
+                .iter_mut()
+                .map(|s| IoSliceMut::new(&mut *s))
+                .collect();
+            f.sock_recv(&mut ioslices, RiFlags::from(ri_flags)).await?
+        };
 
-        let (bytes_read, roflags) = f.sock_recv(&mut ioslices, RiFlags::from(ri_flags)).await?;
-        Ok((types::Size::try_from(bytes_read)?, roflags.into()))
+        Ok((types::Size::try_from(bytes_read)?, ro_flags.into()))
     }
 
     async fn sock_send<'a>(
@@ -1101,16 +1249,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
             .get_file_mut(u32::from(fd))?
             .get_cap_mut(FileCaps::WRITE)?;
 
-        let guest_slices: Vec<wiggle::GuestSlice<u8>> = si_data
+        let guest_slices: Vec<wiggle::GuestCow<u8>> = si_data
             .iter()
             .map(|iov_ptr| {
                 let iov_ptr = iov_ptr?;
                 let iov: types::Ciovec = iov_ptr.read()?;
-                Ok(iov
-                    .buf
-                    .as_array(iov.buf_len)
-                    .as_slice()?
-                    .expect("cannot use with shared memories; see https://github.com/bytecodealliance/wasmtime/issues/5235 (TODO)"))
+                Ok(iov.buf.as_array(iov.buf_len).as_cow()?)
             })
             .collect::<Result<_, Error>>()?;
 
diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs
index c1891ff80..3f4782434 100644
--- a/crates/wasmtime/src/linker.rs
+++ b/crates/wasmtime/src/linker.rs
@@ -5,7 +5,7 @@ use crate::{
     AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance,
     IntoFunc, Module, StoreContextMut, Val, ValRaw,
 };
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
 use log::warn;
 use std::collections::hash_map::{Entry, HashMap};
 #[cfg(feature = "async")]
@@ -263,15 +263,10 @@ impl<T> Linker<T> {
     #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
     pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> {
         for import in module.imports() {
-            if self._get_by_import(&import).is_err() {
-                if let ExternType::Func(func_ty) = import.ty() {
-                    let err_msg = format!(
-                        "unknown import: `{}::{}` has not been defined",
-                        import.module(),
-                        import.name(),
-                    );
+            if let Err(import_err) = self._get_by_import(&import) {
+                if let ExternType::Func(func_ty) = import_err.ty() {
                     self.func_new(import.module(), import.name(), func_ty, move |_, _, _| {
-                        bail!("{err_msg}")
+                        bail!(import_err.clone());
                     })?;
                 }
             }
@@ -973,7 +968,9 @@ impl<T> Linker<T> {
     ///
     /// This method can fail because an import may not be found, or because
     /// instantiation itself may fail. For information on instantiation
-    /// failures see [`Instance::new`].
+    /// failures see [`Instance::new`]. If an import is not found, the error
+    /// may be downcast to an [`UnknownImportError`].
+    ///
     ///
     /// # Panics
     ///
@@ -1035,6 +1032,11 @@ impl<T> Linker<T> {
     /// returned [`InstancePre`] represents a ready-to-be-instantiated module,
     /// which can also be instantiated multiple times if desired.
     ///
+    /// # Errors
+    ///
+    /// Returns an error which may be downcast to an [`UnknownImportError`] if
+    /// the module has any unresolvable imports.
+    ///
     /// # Panics
     ///
     /// This method will panic if any item defined in this linker used by
@@ -1085,7 +1087,7 @@ impl<T> Linker<T> {
         let imports = module
             .imports()
             .map(|import| self._get_by_import(&import))
-            .collect::<Result<_>>()?;
+            .collect::<Result<_, _>>()?;
         unsafe { InstancePre::new(store, module, imports) }
     }
 
@@ -1166,20 +1168,11 @@ impl<T> Linker<T> {
         Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) })
     }
 
-    fn _get_by_import(&self, import: &ImportType) -> anyhow::Result<Definition> {
-        fn undef_err(missing_import: &str) -> anyhow::Error {
-            anyhow!("unknown import: `{}` has not been defined", missing_import)
-        }
-
-        if let Some(item) = self._get(import.module(), import.name()) {
-            return Ok(item.clone());
+    fn _get_by_import(&self, import: &ImportType) -> Result<Definition, UnknownImportError> {
+        match self._get(import.module(), import.name()) {
+            Some(item) => Ok(item.clone()),
+            None => Err(UnknownImportError::new(import)),
         }
-
-        Err(undef_err(&format!(
-            "{}::{}",
-            import.module(),
-            import.name()
-        )))
     }
 
     /// Returns the "default export" of a module.
@@ -1294,3 +1287,51 @@ impl ModuleKind {
         }
     }
 }
+
+/// Error for an unresolvable import.
+///
+/// Returned - wrapped in an [`anyhow::Error`] - by [`Linker::instantiate`] and
+/// related methods for modules with unresolvable imports.
+#[derive(Clone, Debug)]
+pub struct UnknownImportError {
+    module: String,
+    name: String,
+    ty: ExternType,
+}
+
+impl UnknownImportError {
+    fn new(import: &ImportType) -> Self {
+        Self {
+            module: import.module().to_string(),
+            name: import.name().to_string(),
+            ty: import.ty(),
+        }
+    }
+
+    /// Returns the module name that the unknown import was expected to come from.
+    pub fn module(&self) -> &str {
+        &self.module
+    }
+
+    /// Returns the field name of the module that the unknown import was expected to come from.
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    /// Returns the type of the unknown import.
+    pub fn ty(&self) -> ExternType {
+        self.ty.clone()
+    }
+}
+
+impl std::fmt::Display for UnknownImportError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "unknown import: `{}::{}` has not been defined",
+            self.module, self.name,
+        )
+    }
+}
+
+impl std::error::Error for UnknownImportError {}
diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs
index 6e2883978..9b7451bd7 100644
--- a/crates/wiggle/src/lib.rs
+++ b/crates/wiggle/src/lib.rs
@@ -482,6 +482,11 @@ impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
     {
         GuestPtr::new(self.mem, (self.pointer, elems))
     }
+
+    /// Check if this pointer references WebAssembly shared memory.
+    pub fn is_shared_memory(&self) -> bool {
+        self.mem.is_shared_memory()
+    }
 }
 
 impl<'a, T> GuestPtr<'a, [T]> {
@@ -512,6 +517,24 @@ impl<'a, T> GuestPtr<'a, [T]> {
         (0..self.len()).map(move |i| base.add(i))
     }
 
+    /// Attempts to create a [`GuestCow<'_, T>`] from this pointer, performing
+    /// bounds checks and type validation. Whereas [`GuestPtr::as_slice`] will
+    /// fail with `None` if attempting to access Wasm shared memory, this call
+    /// will succeed: if used on shared memory, this function will copy the
+    /// slice into [`GuestCow::Copied`]. If the memory is non-shared, this
+    /// returns a [`GuestCow::Borrowed`] (a thin wrapper over [`GuestSlice<'_,
+    /// T>]`).
+    pub fn as_cow(&self) -> Result<GuestCow<'a, T>, GuestError>
+    where
+        T: GuestTypeTransparent<'a> + Copy + 'a,
+    {
+        match self.as_unsafe_slice_mut()?.shared_borrow() {
+            UnsafeBorrowResult::Ok(slice) => Ok(GuestCow::Borrowed(slice)),
+            UnsafeBorrowResult::Shared(_) => Ok(GuestCow::Copied(self.to_vec()?)),
+            UnsafeBorrowResult::Err(e) => Err(e),
+        }
+    }
+
     /// Attempts to create a [`GuestSlice<'_, T>`] from this pointer, performing
     /// bounds checks and type validation. The `GuestSlice` is a smart pointer
     /// that can be used as a `&[T]` via the `Deref` trait. The region of memory
@@ -524,7 +547,8 @@ impl<'a, T> GuestPtr<'a, [T]> {
     /// any checks fail then `GuestError` will be returned.
     ///
     /// Additionally, because it is `unsafe` to have a `GuestSlice` of shared
-    /// memory, this function will return `None` in this case.
+    /// memory, this function will return `None` in this case (see
+    /// [`GuestPtr::as_cow`]).
     pub fn as_slice(&self) -> Result<Option<GuestSlice<'a, T>>, GuestError>
     where
         T: GuestTypeTransparent<'a>,
@@ -553,11 +577,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
     where
         T: GuestTypeTransparent<'a>,
     {
-        match self.as_unsafe_slice_mut()?.mut_borrow() {
-            UnsafeBorrowResult::Ok(slice) => Ok(Some(slice)),
-            UnsafeBorrowResult::Shared(_) => Ok(None),
-            UnsafeBorrowResult::Err(e) => Err(e),
-        }
+        self.as_unsafe_slice_mut()?.as_slice_mut()
     }
 
     /// Similar to `as_slice_mut`, this function will attempt to create a smart
@@ -614,39 +634,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
     where
         T: GuestTypeTransparent<'a> + Copy + 'a,
     {
-        // Retrieve the slice of memory to copy to, performing the necessary
-        // bounds checks ...
-        let guest_slice = self.as_unsafe_slice_mut()?;
-        // ... length check ...
-        if guest_slice.ptr.len() != slice.len() {
-            return Err(GuestError::SliceLengthsDiffer);
-        }
-        if slice.len() == 0 {
-            return Ok(());
-        }
-
-        // ... and copy the bytes.
-        match guest_slice.mut_borrow() {
-            UnsafeBorrowResult::Ok(mut dst) => dst.copy_from_slice(slice),
-            UnsafeBorrowResult::Shared(guest_slice) => {
-                // SAFETY: in the shared memory case, we copy and accept that
-                // the guest data may be concurrently modified. TODO: audit that
-                // this use of `std::ptr::copy` is safe with shared memory
-                // (https://github.com/bytecodealliance/wasmtime/issues/4203)
-                //
-                // Also note that the validity of `guest_slice` has already been
-                // determined by the `as_unsafe_slice_mut` call above.
-                unsafe {
-                    std::ptr::copy(
-                        slice.as_ptr(),
-                        guest_slice.ptr[0].get(),
-                        guest_slice.ptr.len(),
-                    )
-                };
-            }
-            UnsafeBorrowResult::Err(e) => return Err(e),
-        }
-        Ok(())
+        self.as_unsafe_slice_mut()?.copy_from_slice(slice)
     }
 
     /// Returns a `GuestPtr` pointing to the base of the array for the interior
@@ -772,6 +760,30 @@ impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
     }
 }
 
+/// A smart pointer for distinguishing between different kinds of Wasm memory:
+/// shared and non-shared.
+///
+/// As with `GuestSlice`, this is usable as a `&'a [T]` via [`std::ops::Deref`].
+/// The major difference is that, for shared memories, the memory will be copied
+/// out of Wasm linear memory to avoid the possibility of concurrent mutation by
+/// another thread. This extra copy exists solely to maintain the Rust
+/// guarantees regarding `&[T]`.
+pub enum GuestCow<'a, T> {
+    Borrowed(GuestSlice<'a, T>),
+    Copied(Vec<T>),
+}
+
+impl<'a, T> std::ops::Deref for GuestCow<'a, T> {
+    type Target = [T];
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            GuestCow::Borrowed(s) => s,
+            GuestCow::Copied(s) => s,
+        }
+    }
+}
+
 /// A smart pointer to an shareable slice in guest memory.
 ///
 /// Usable as a `&'a [T]` via [`std::ops::Deref`].
@@ -868,7 +880,72 @@ pub struct UnsafeGuestSlice<'a, T> {
     mem: &'a dyn GuestMemory,
 }
 
+// SAFETY: `UnsafeGuestSlice` can be used across an `await` and therefore must
+// be `Send` and `Sync`. As with `GuestSlice` and friends, we mirror the
+// `Send`/`Sync` impls due to the interior usage of `&[UnsafeCell<T>]`.
+unsafe impl<T: Sync> Sync for UnsafeGuestSlice<'_, T> {}
+unsafe impl<T: Send> Send for UnsafeGuestSlice<'_, T> {}
+
 impl<'a, T> UnsafeGuestSlice<'a, T> {
+    /// See `GuestPtr::copy_from_slice`.
+    pub fn copy_from_slice(self, slice: &[T]) -> Result<(), GuestError>
+    where
+        T: GuestTypeTransparent<'a> + Copy + 'a,
+    {
+        // Check the length...
+        if self.ptr.len() != slice.len() {
+            return Err(GuestError::SliceLengthsDiffer);
+        }
+        if slice.len() == 0 {
+            return Ok(());
+        }
+
+        // ... and copy the bytes.
+        match self.mut_borrow() {
+            UnsafeBorrowResult::Ok(mut dst) => dst.copy_from_slice(slice),
+            UnsafeBorrowResult::Shared(guest_slice) => {
+                // SAFETY: in the shared memory case, we copy and accept that
+                // the guest data may be concurrently modified. TODO: audit that
+                // this use of `std::ptr::copy` is safe with shared memory
+                // (https://github.com/bytecodealliance/wasmtime/issues/4203)
+                //
+                // Also note that the validity of `guest_slice` has already been
+                // determined by the `as_unsafe_slice_mut` call above.
+                unsafe {
+                    std::ptr::copy(
+                        slice.as_ptr(),
+                        guest_slice.ptr[0].get(),
+                        guest_slice.ptr.len(),
+                    )
+                };
+            }
+            UnsafeBorrowResult::Err(e) => return Err(e),
+        }
+        Ok(())
+    }
+
+    /// Return the number of items in this slice.
+    pub fn len(&self) -> usize {
+        self.ptr.len()
+    }
+
+    /// Check if this slice comes from WebAssembly shared memory.
+    pub fn is_shared_memory(&self) -> bool {
+        self.mem.is_shared_memory()
+    }
+
+    /// See `GuestPtr::as_slice_mut`.
+    pub fn as_slice_mut(self) -> Result<Option<GuestSliceMut<'a, T>>, GuestError>
+    where
+        T: GuestTypeTransparent<'a>,
+    {
+        match self.mut_borrow() {
+            UnsafeBorrowResult::Ok(slice) => Ok(Some(slice)),
+            UnsafeBorrowResult::Shared(_) => Ok(None),
+            UnsafeBorrowResult::Err(e) => Err(e),
+        }
+    }
+
     /// Transform an `unsafe` guest slice to a [`GuestSliceMut`].
     ///
     /// # Safety
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index e1f68cfac..f8443092d 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -33,6 +33,7 @@
   - [.NET](./lang-dotnet.md)
   - [Go](./lang-go.md)
   - [Bash](./lang-bash.md)
+  - [Ruby](./lang-ruby.md)
 - [Using the `wasmtime` CLI](./cli.md)
   - [Installation](./cli-install.md)
   - [CLI Options](./cli-options.md)
diff --git a/docs/examples-markdown.md b/docs/examples-markdown.md
index a6fea7197..8e1ba144c 100644
--- a/docs/examples-markdown.md
+++ b/docs/examples-markdown.md
@@ -9,6 +9,12 @@ cargo new --bin rust_wasi_markdown_parser
 cd rust_wasi_markdown_parser
 ```
 
+Also, we need to add the `structopt` and `pulldown_cmark` crates to our project:
+
+```bash
+cargo add structopt pulldown_cmark
+```
+
 Then, we will open the `src/main.rs` and enter the following contents. Please see the comments to understand what our program will be doing.
 
 ## `src/main.rs`
@@ -50,7 +56,7 @@ wasmtime --dir . target/wasm32-wasi/debug/rust_wasi_markdown_parser.wasm -- ./ex
 
 Which should look like the following:
 
-```html 
+```html
 <h1>Hello!</h1>
 <p>I am example markdown for this demo!</p>
 ```
diff --git a/docs/lang-ruby.md b/docs/lang-ruby.md
new file mode 100644
index 000000000..6b227d209
--- /dev/null
+++ b/docs/lang-ruby.md
@@ -0,0 +1,63 @@
+# Using WebAssembly from Ruby
+
+Wasmtime [is available on RubyGems](https://rubygems.org/gems/wasmtime) and can
+be used programmatically to interact with Wasm modules. This guide will go over
+installing the Wasmtime gem and running a simple Wasm module from Ruby.
+
+Make sure you've got Ruby 3.0 or newer installed locally, and we can get
+started!
+
+## Getting started and simple example
+
+First, copy this example WebAssembly text module into your project. It exports
+a function for calculating the greatest common denominator of two numbers.
+
+```wat
+{{#include ../examples/gcd.wat}}
+```
+
+Next, install the Wasmtime Ruby gems by either adding it your project's
+`Gemfile`:
+
+```bash
+bundle add wasmtime
+```
+
+Or by using the `gem` command directly:
+
+```bash
+gem install wasmtime
+```
+
+The gem has a Rust-based native extension, but thanks to precompiled gems, you
+should not have to compile anything. It'll just work!
+
+Now that you have the Wasmtime gem installed, let's create a Ruby script to
+execute the `gcd` module from before.
+
+```ruby
+require "wasmtime"
+
+engine = Wasmtime::Engine.new
+mod = Wasmtime::Module.from_file(engine, "gcd.wat")
+store = Wasmtime::Store.new(engine)
+instance = Wasmtime::Instance.new(store, mod)
+
+puts "gcd(27, 6) = #{instance.invoke("gcd", 27, 6)}"
+```
+
+This script should output
+
+```bash
+gcd(27, 6) = 3
+```
+
+If this is the output you see, congrats! You've successfully ran your first
+WebAssembly code in Ruby!
+
+## More examples and contributing
+
+To learn more, check out the [more advanced examples](https://github.com/bytecodealliance/wasmtime-rb/tree/main/examples)
+and the [API documentation](https://bytecodealliance.github.io/wasmtime-rb/latest/).
+If you have any questions, do not hesitate to open an issue on the
+[GitHub repository](https://github.com/bytecodealliance/wasmtime-rb).
diff --git a/docs/lang.md b/docs/lang.md
index 2c1b0c6a8..ae3d0a13c 100644
--- a/docs/lang.md
+++ b/docs/lang.md
@@ -10,3 +10,4 @@ through a C API for a number of other languages too:
 * [.NET](lang-dotnet.md)
 * [Go](lang-go.md)
 * [Bash](lang-bash.md)
+* [Ruby](lang-ruby.md)
diff --git a/docs/stability-release.md b/docs/stability-release.md
index b2f525819..54add880b 100644
--- a/docs/stability-release.md
+++ b/docs/stability-release.md
@@ -56,6 +56,7 @@ release is made, such as:
 * [`wasmtime-py`](https://github.com/bytecodealliance/wasmtime-py)
 * [`wasmtime-go`](https://github.com/bytecodealliance/wasmtime-go)
 * [`wasmtime-cpp`](https://github.com/bytecodealliance/wasmtime-cpp)
+* [`wasmtime-rb`](https://github.com/bytecodealliance/wasmtime-rb)
 
 Note, though, that bugs and security issues in these projects do not at this
 time warrant patch releases for Wasmtime.


Test patch:
diff --git a/cranelift/filetests/filetests/isa/aarch64/condbr.clif b/cranelift/filetests/filetests/isa/aarch64/condbr.clif
index 1975528e3..e23bd6165 100644
--- a/cranelift/filetests/filetests/isa/aarch64/condbr.clif
+++ b/cranelift/filetests/filetests/isa/aarch64/condbr.clif
@@ -152,7 +152,8 @@ block0(v0: i128, v1: i128):
 function %f(i64, i64) -> i64 {
 block0(v0: i64, v1: i64):
   v2 = icmp eq v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block2
 
 block1:
@@ -177,7 +178,8 @@ block2:
 function %f(i64, i64) -> i64 {
 block0(v0: i64, v1: i64):
   v2 = icmp eq v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -239,7 +241,8 @@ block1:
 function %i128_bricmp_eq(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp eq v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -260,7 +263,8 @@ block1:
 function %i128_bricmp_ne(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp ne v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -281,7 +285,8 @@ block1:
 function %i128_bricmp_slt(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp slt v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -306,7 +311,8 @@ block1:
 function %i128_bricmp_ult(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp ult v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -331,7 +337,8 @@ block1:
 function %i128_bricmp_sle(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp sle v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -357,7 +364,8 @@ block1:
 function %i128_bricmp_ule(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp ule v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -383,7 +391,8 @@ block1:
 function %i128_bricmp_sgt(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp sgt v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -408,7 +417,8 @@ block1:
 function %i128_bricmp_ugt(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp ugt v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -433,7 +443,8 @@ block1:
 function %i128_bricmp_sge(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp sge v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
@@ -459,7 +470,8 @@ block1:
 function %i128_bricmp_uge(i128, i128) {
 block0(v0: i128, v1: i128):
   v2 = icmp uge v0, v1
-  brnz v2, block1
+  v3 = uextend.i32 v2
+  brnz v3, block1
   jump block1
 
 block1:
diff --git a/cranelift/filetests/filetests/isa/aarch64/select.clif b/cranelift/filetests/filetests/isa/aarch64/select.clif
new file mode 100644
index 000000000..29406b6a9
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/aarch64/select.clif
@@ -0,0 +1,29 @@
+test compile precise-output
+target aarch64
+
+function %f0(i32, i32, i64, i64) -> i64 {
+block0(v0: i32, v1: i32, v2: i64, v3: i64):
+    v4 = icmp eq v0, v1
+    v5 = uextend.i32 v4
+    v6 = select.i64 v5, v2, v3
+    return v6
+}
+
+; block0:
+;   subs wzr, w0, w1
+;   csel x0, x2, x3, eq
+;   ret
+
+function %f0(f32, f32, i64, i64) -> i64 {
+block0(v0: f32, v1: f32, v2: i64, v3: i64):
+    v4 = fcmp eq v0, v1
+    v5 = uextend.i32 v4
+    v6 = select.i64 v5, v2, v3
+    return v6
+}
+
+; block0:
+;   fcmp s0, s1
+;   csel x0, x0, x1, eq
+;   ret
+
diff --git a/cranelift/filetests/filetests/isa/x64/branches.clif b/cranelift/filetests/filetests/isa/x64/branches.clif
index 2e3f54249..1276b7436 100644
--- a/cranelift/filetests/filetests/isa/x64/branches.clif
+++ b/cranelift/filetests/filetests/isa/x64/branches.clif
@@ -328,3 +328,69 @@ block202:
 ;   popq    %rbp
 ;   ret
 
+
+function %br_i8_icmp(i32, i32) -> i32 {
+block0(v0: i32, v1: i32):
+  v2 = icmp eq v0, v1
+  v3 = uextend.i32 v2
+  brnz v3, block1
+  jump block2
+
+block1:
+  v4 = iconst.i32 1
+  return v4
+
+block2:
+  v5 = iconst.i32 2
+  return v5
+}
+
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   cmpl    %esi, %edi
+;   jz      label1; j label2
+; block1:
+;   movl    $1, %eax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+; block2:
+;   movl    $2, %eax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+
+function %br_i8_fcmp(f32, f32) -> i32 {
+block0(v0: f32, v1: f32):
+  v2 = fcmp eq v0, v1
+  v3 = uextend.i32 v2
+  brnz v3, block1
+  jump block2
+
+block1:
+  v4 = iconst.i32 1
+  return v4
+
+block2:
+  v5 = iconst.i32 2
+  return v5
+}
+
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   ucomiss %xmm1, %xmm0
+;   jp      label2
+;   jnz     label2; j label1
+; block1:
+;   movl    $1, %eax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+; block2:
+;   movl    $2, %eax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+
diff --git a/cranelift/filetests/filetests/isa/x64/select.clif b/cranelift/filetests/filetests/isa/x64/select.clif
new file mode 100644
index 000000000..fab8ec38d
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/x64/select.clif
@@ -0,0 +1,41 @@
+test compile precise-output
+target x86_64
+
+function %f0(i32, i32, i64, i64) -> i64 {
+block0(v0: i32, v1: i32, v2: i64, v3: i64):
+    v4 = icmp eq v0, v1
+    v5 = uextend.i32 v4
+    v6 = select.i64 v5, v2, v3
+    return v6
+}
+
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   cmpl    %esi, %edi
+;   movq    %rcx, %rax
+;   cmovzq  %rdx, %rax, %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+
+
+function %f0(f32, f32, i64, i64) -> i64 {
+block0(v0: f32, v1: f32, v2: i64, v3: i64):
+    v4 = fcmp eq v0, v1
+    v5 = uextend.i32 v4
+    v6 = select.i64 v5, v2, v3
+    return v6
+}
+
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   ucomiss %xmm0, %xmm1
+;   movq    %rdi, %rax
+;   cmovnzq %rsi, %rax, %rax
+;   cmovpq  %rsi, %rax, %rax
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+
diff --git a/cranelift/filetests/filetests/runtests/br_table.clif b/cranelift/filetests/filetests/runtests/br_table.clif
index d6b9f4bc9..8d08bf355 100644
--- a/cranelift/filetests/filetests/runtests/br_table.clif
+++ b/cranelift/filetests/filetests/runtests/br_table.clif
@@ -39,4 +39,24 @@ block5(v5: i32):
 ; run: %br_table_i32(4) == 8
 ; run: %br_table_i32(5) == 9
 ; run: %br_table_i32(6) == 10
-; run: %br_table_i32(-1) == 3
\ No newline at end of file
+; run: %br_table_i32(-1) == 3
+
+
+
+; RISC-V had a bug where having a br_table on a cold block would cause a segfault
+; See #5496 for more details.
+function %br_table_cold_block(i32) -> i32 system_v {
+    jt0 = jump_table []
+
+block0(v0: i32):
+    jump block1
+
+block1 cold:
+    br_table v0, block2, jt0
+
+block2:
+    v1 = iconst.i32 0
+    return v1
+}
+; run: %br_table_cold_block(0) == 0
+; run: %br_table_cold_block(1) == 0
\ No newline at end of file
diff --git a/cranelift/filetests/filetests/runtests/integer-minmax.clif b/cranelift/filetests/filetests/runtests/integer-minmax.clif
index 73edeeec3..d030e20c4 100644
--- a/cranelift/filetests/filetests/runtests/integer-minmax.clif
+++ b/cranelift/filetests/filetests/runtests/integer-minmax.clif
@@ -1,6 +1,6 @@
 test interpret
 test run
-; target aarch64
+target aarch64
 ; target s390x
 target x86_64
 target riscv64
@@ -134,4 +134,19 @@ block0(v0: i64, v1: i64):
 ; run: %umin_max_i64(1, 2) == [1, 2]
 ; run: %umin_max_i64(2, 1) == [1, 2]
 ; run: %umin_max_i64(0x7fffffff, 0x80000000) == [0x7fffffff, 0x80000000]
-; run: %umin_max_i64(0x7fffffffffffffff, 0x8000000000000000) == [0x7fffffffffffffff, 0x8000000000000000]
\ No newline at end of file
+; run: %umin_max_i64(0x7fffffffffffffff, 0x8000000000000000) == [0x7fffffffffffffff, 0x8000000000000000]
+
+; make sure that upper/uninitialized parts of operand registers do not leak
+; inside of the comparison
+function %umin_max_i32_trunc_to_i8(i32, i32) -> i8, i8 {
+block0(v0: i32, v1: i32):
+    v2 = ireduce.i8 v0
+    v3 = ireduce.i8 v1
+    v4 = umin.i8 v2, v3
+    v5 = umax.i8 v2, v3
+    return v4, v5
+}
+; run: %umin_max_i32_trunc_to_i8(1, 2) == [1, 2]
+; run: %umin_max_i32_trunc_to_i8(0xBB01, 0xAA02) == [1, 2]
+; run: %umin_max_i32_trunc_to_i8(0xBB02, 0xABCD0001) == [1, 2]
+; run: %umin_max_i32_trunc_to_i8(0x1234567f, 0x12345680) == [0x7f, 0x80]
diff --git a/crates/test-programs/wasi-tests/Cargo.lock b/crates/test-programs/wasi-tests/Cargo.lock
index 004fa00cc..d1d241ba8 100644
--- a/crates/test-programs/wasi-tests/Cargo.lock
+++ b/crates/test-programs/wasi-tests/Cargo.lock
@@ -16,9 +16,9 @@ checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
 
 [[package]]
 name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasi-tests"
diff --git a/crates/test-programs/wasi-tests/Cargo.toml b/crates/test-programs/wasi-tests/Cargo.toml
index 53e4691bf..64a5ca3db 100644
--- a/crates/test-programs/wasi-tests/Cargo.toml
+++ b/crates/test-programs/wasi-tests/Cargo.toml
@@ -7,7 +7,7 @@ publish = false
 
 [dependencies]
 libc = "0.2.65"
-wasi = "0.10.2"
+wasi = "0.11.0"
 once_cell = "1.12"
 
 # This crate is built with the wasm32-wasi target, so it's separate
diff --git a/crates/test-programs/wasi-tests/src/bin/close_preopen.rs b/crates/test-programs/wasi-tests/src/bin/close_preopen.rs
index 9066f0645..9f955f44a 100644
--- a/crates/test-programs/wasi-tests/src/bin/close_preopen.rs
+++ b/crates/test-programs/wasi-tests/src/bin/close_preopen.rs
@@ -8,17 +8,14 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
 
     // Try to close a preopened directory handle.
     assert_errno!(
-        wasi::fd_close(pre_fd)
-            .expect_err("closing a preopened file descriptor")
-            .raw_error(),
+        wasi::fd_close(pre_fd).expect_err("closing a preopened file descriptor"),
         wasi::ERRNO_NOTSUP
     );
 
     // Try to renumber over a preopened directory handle.
     assert_errno!(
         wasi::fd_renumber(dir_fd, pre_fd)
-            .expect_err("renumbering over a preopened file descriptor")
-            .raw_error(),
+            .expect_err("renumbering over a preopened file descriptor"),
         wasi::ERRNO_NOTSUP
     );
 
@@ -33,8 +30,7 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
     // Try to renumber a preopened directory handle.
     assert_errno!(
         wasi::fd_renumber(pre_fd, dir_fd)
-            .expect_err("renumbering over a preopened file descriptor")
-            .raw_error(),
+            .expect_err("renumbering over a preopened file descriptor"),
         wasi::ERRNO_NOTSUP
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/dangling_symlink.rs b/crates/test-programs/wasi-tests/src/bin/dangling_symlink.rs
index bfc9095d3..f7538fc97 100644
--- a/crates/test-programs/wasi-tests/src/bin/dangling_symlink.rs
+++ b/crates/test-programs/wasi-tests/src/bin/dangling_symlink.rs
@@ -9,8 +9,7 @@ unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) {
         // Try to open it as a directory with O_NOFOLLOW.
         assert_errno!(
             wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
-                .expect_err("opening a dangling symlink as a directory")
-                .raw_error(),
+                .expect_err("opening a dangling symlink as a directory"),
             wasi::ERRNO_NOTDIR,
             wasi::ERRNO_LOOP
         );
@@ -18,8 +17,7 @@ unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) {
         // Try to open it as a file with O_NOFOLLOW.
         assert_errno!(
             wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
-                .expect_err("opening a dangling symlink as a file")
-                .raw_error(),
+                .expect_err("opening a dangling symlink as a file"),
             wasi::ERRNO_LOOP
         );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/directory_seek.rs b/crates/test-programs/wasi-tests/src/bin/directory_seek.rs
index ab4b0dbc8..85b96e6a1 100644
--- a/crates/test-programs/wasi-tests/src/bin/directory_seek.rs
+++ b/crates/test-programs/wasi-tests/src/bin/directory_seek.rs
@@ -23,9 +23,7 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) {
 
     // Attempt to seek.
     assert_errno!(
-        wasi::fd_seek(fd, 0, wasi::WHENCE_CUR)
-            .expect_err("seek on a directory")
-            .raw_error(),
+        wasi::fd_seek(fd, 0, wasi::WHENCE_CUR).expect_err("seek on a directory"),
         wasi::ERRNO_BADF
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/fd_filestat_get.rs b/crates/test-programs/wasi-tests/src/bin/fd_filestat_get.rs
index d2ba27a1d..3cff4e722 100644
--- a/crates/test-programs/wasi-tests/src/bin/fd_filestat_get.rs
+++ b/crates/test-programs/wasi-tests/src/bin/fd_filestat_get.rs
@@ -1,5 +1,4 @@
 unsafe fn test_fd_filestat_get() {
-
     let stat = wasi::fd_filestat_get(libc::STDIN_FILENO as u32).expect("failed filestat 0");
     assert_eq!(stat.size, 0, "stdio size should be 0");
     assert_eq!(stat.atim, 0, "stdio atim should be 0");
diff --git a/crates/test-programs/wasi-tests/src/bin/file_seek_tell.rs b/crates/test-programs/wasi-tests/src/bin/file_seek_tell.rs
index 1272d4e10..a3d3711b9 100644
--- a/crates/test-programs/wasi-tests/src/bin/file_seek_tell.rs
+++ b/crates/test-programs/wasi-tests/src/bin/file_seek_tell.rs
@@ -57,8 +57,7 @@ unsafe fn test_file_seek_tell(dir_fd: wasi::Fd) {
     // Seek before byte 0 is an error though
     assert_errno!(
         wasi::fd_seek(file_fd, -2000, wasi::WHENCE_CUR)
-            .expect_err("seeking before byte 0 should be an error")
-            .raw_error(),
+            .expect_err("seeking before byte 0 should be an error"),
         wasi::ERRNO_INVAL
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/interesting_paths.rs b/crates/test-programs/wasi-tests/src/bin/interesting_paths.rs
index 70aaae3ab..16954c92f 100644
--- a/crates/test-programs/wasi-tests/src/bin/interesting_paths.rs
+++ b/crates/test-programs/wasi-tests/src/bin/interesting_paths.rs
@@ -14,8 +14,7 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
     // Now open it with an absolute path.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "/dir/nested/file", 0, 0, 0, 0)
-            .expect_err("opening a file with an absolute path")
-            .raw_error(),
+            .expect_err("opening a file with an absolute path"),
         wasi::ERRNO_PERM
     );
 
@@ -39,8 +38,7 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
     // Now open it with a trailing NUL.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "dir/nested/file\0", 0, 0, 0, 0)
-            .expect_err("opening a file with a trailing NUL")
-            .raw_error(),
+            .expect_err("opening a file with a trailing NUL"),
         wasi::ERRNO_INVAL,
         wasi::ERRNO_ILSEQ
     );
@@ -48,8 +46,7 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
     // Now open it with a trailing slash.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "dir/nested/file/", 0, 0, 0, 0)
-            .expect_err("opening a file with a trailing slash should fail")
-            .raw_error(),
+            .expect_err("opening a file with a trailing slash should fail"),
         wasi::ERRNO_NOTDIR,
         wasi::ERRNO_NOENT
     );
@@ -57,8 +54,7 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
     // Now open it with trailing slashes.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "dir/nested/file///", 0, 0, 0, 0)
-            .expect_err("opening a file with trailing slashes should fail")
-            .raw_error(),
+            .expect_err("opening a file with trailing slashes should fail"),
         wasi::ERRNO_NOTDIR,
         wasi::ERRNO_NOENT
     );
@@ -85,8 +81,7 @@ unsafe fn test_interesting_paths(dir_fd: wasi::Fd, arg: &str) {
     let bad_path = format!("dir/nested/../../../{}/dir/nested/file", arg);
     assert_errno!(
         wasi::path_open(dir_fd, 0, &bad_path, 0, 0, 0, 0)
-            .expect_err("opening a file with too many \"..\"s in the path should fail")
-            .raw_error(),
+            .expect_err("opening a file with too many \"..\"s in the path should fail"),
         wasi::ERRNO_PERM
     );
     wasi::path_unlink_file(dir_fd, "dir/nested/file")
diff --git a/crates/test-programs/wasi-tests/src/bin/nofollow_errors.rs b/crates/test-programs/wasi-tests/src/bin/nofollow_errors.rs
index 11fdffc03..86c6f3858 100644
--- a/crates/test-programs/wasi-tests/src/bin/nofollow_errors.rs
+++ b/crates/test-programs/wasi-tests/src/bin/nofollow_errors.rs
@@ -11,8 +11,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
     // Try to open it as a directory with O_NOFOLLOW again.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
-            .expect_err("opening a directory symlink as a directory should fail")
-            .raw_error(),
+            .expect_err("opening a directory symlink as a directory should fail"),
         wasi::ERRNO_LOOP,
         wasi::ERRNO_NOTDIR
     );
@@ -20,8 +19,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
     // Try to open it with just O_NOFOLLOW.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
-            .expect_err("opening a symlink with O_NOFOLLOW should fail")
-            .raw_error(),
+            .expect_err("opening a symlink with O_NOFOLLOW should fail"),
         wasi::ERRNO_LOOP,
         wasi::ERRNO_ACCES
     );
@@ -56,8 +54,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
     // Try to open it as a directory with O_NOFOLLOW again.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
-            .expect_err("opening a directory symlink as a directory should fail")
-            .raw_error(),
+            .expect_err("opening a directory symlink as a directory should fail"),
         wasi::ERRNO_LOOP,
         wasi::ERRNO_NOTDIR
     );
@@ -65,8 +62,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
     // Try to open it with just O_NOFOLLOW.
     assert_errno!(
         wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
-            .expect_err("opening a symlink with NOFOLLOW should fail")
-            .raw_error(),
+            .expect_err("opening a symlink with NOFOLLOW should fail"),
         wasi::ERRNO_LOOP
     );
 
@@ -81,8 +77,7 @@ unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) {
             0,
             0,
         )
-        .expect_err("opening a symlink to a file as a directory")
-        .raw_error(),
+        .expect_err("opening a symlink to a file as a directory"),
         wasi::ERRNO_NOTDIR
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/path_filestat.rs b/crates/test-programs/wasi-tests/src/bin/path_filestat.rs
index a53d7b0d6..2b1629e40 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_filestat.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_filestat.rs
@@ -67,8 +67,7 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
                 0,
                 wasi::FDFLAGS_SYNC,
             )
-            .expect_err("FDFLAGS_SYNC not supported by platform")
-            .raw_error(),
+            .expect_err("FDFLAGS_SYNC not supported by platform"),
             wasi::ERRNO_NOTSUP
         );
     }
@@ -95,8 +94,7 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
             new_mtim,
             wasi::FSTFLAGS_MTIM | wasi::FSTFLAGS_MTIM_NOW,
         )
-        .expect_err("MTIM and MTIM_NOW can't both be set")
-        .raw_error(),
+        .expect_err("MTIM and MTIM_NOW can't both be set"),
         wasi::ERRNO_INVAL
     );
 
@@ -118,8 +116,7 @@ unsafe fn test_path_filestat(dir_fd: wasi::Fd) {
             0,
             wasi::FSTFLAGS_ATIM | wasi::FSTFLAGS_ATIM_NOW,
         )
-        .expect_err("ATIM & ATIM_NOW can't both be set")
-        .raw_error(),
+        .expect_err("ATIM & ATIM_NOW can't both be set"),
         wasi::ERRNO_INVAL
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/path_link.rs b/crates/test-programs/wasi-tests/src/bin/path_link.rs
index 5932754d9..1216c334f 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_link.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_link.rs
@@ -101,8 +101,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
-            .expect_err("creating a link to existing path should fail")
-            .raw_error(),
+            .expect_err("creating a link to existing path should fail"),
         wasi::ERRNO_EXIST
     );
     wasi::path_unlink_file(dir_fd, "link").expect("removing a file");
@@ -110,8 +109,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
     // Create a link to itself
     assert_errno!(
         wasi::path_link(dir_fd, 0, "file", dir_fd, "file")
-            .expect_err("creating a link to itself should fail")
-            .raw_error(),
+            .expect_err("creating a link to itself should fail"),
         wasi::ERRNO_EXIST
     );
 
@@ -120,8 +118,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
-            .expect_err("creating a link where target is a directory should fail")
-            .raw_error(),
+            .expect_err("creating a link where target is a directory should fail"),
         wasi::ERRNO_EXIST
     );
     wasi::path_remove_directory(dir_fd, "link").expect("removing a dir");
@@ -132,8 +129,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link")
-            .expect_err("creating a link to a directory should fail")
-            .raw_error(),
+            .expect_err("creating a link to a directory should fail"),
         wasi::ERRNO_PERM,
         wasi::ERRNO_ACCES
     );
@@ -143,8 +139,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
     // Create a link to a file with trailing slash
     assert_errno!(
         wasi::path_link(dir_fd, 0, "file", dir_fd, "link/")
-            .expect_err("creating a link to a file with trailing slash should fail")
-            .raw_error(),
+            .expect_err("creating a link to a file with trailing slash should fail"),
         wasi::ERRNO_NOENT
     );
 
@@ -171,8 +166,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
         assert_errno!(
             wasi::path_link(dir_fd, 0, "file", dir_fd, "symlink")
-                .expect_err("creating a link where target is a dangling symlink")
-                .raw_error(),
+                .expect_err("creating a link where target is a dangling symlink"),
             wasi::ERRNO_EXIST
         );
         wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
@@ -189,8 +183,7 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
                 dir_fd,
                 "link",
             )
-            .expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail")
-            .raw_error(),
+            .expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail"),
             wasi::ERRNO_INVAL
         );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/path_open_create_existing.rs b/crates/test-programs/wasi-tests/src/bin/path_open_create_existing.rs
index 21abc0598..5c4cca201 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_open_create_existing.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_open_create_existing.rs
@@ -13,8 +13,7 @@ unsafe fn test_path_open_create_existing(dir_fd: wasi::Fd) {
             0,
             0,
         )
-        .expect_err("trying to create a file that already exists")
-        .raw_error(),
+        .expect_err("trying to create a file that already exists"),
         wasi::ERRNO_EXIST
     );
     wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
diff --git a/crates/test-programs/wasi-tests/src/bin/path_open_dirfd_not_dir.rs b/crates/test-programs/wasi-tests/src/bin/path_open_dirfd_not_dir.rs
index 7fda00334..74a0d57d2 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_open_dirfd_not_dir.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_open_dirfd_not_dir.rs
@@ -8,8 +8,7 @@ unsafe fn test_dirfd_not_dir(dir_fd: wasi::Fd) {
     // Now try to open a file underneath it as if it were a directory.
     assert_errno!(
         wasi::path_open(file_fd, 0, "foo", wasi::OFLAGS_CREAT, 0, 0, 0)
-            .expect_err("non-directory base fd should get ERRNO_NOTDIR")
-            .raw_error(),
+            .expect_err("non-directory base fd should get ERRNO_NOTDIR"),
         wasi::ERRNO_NOTDIR
     );
     wasi::fd_close(file_fd).expect("closing a file");
diff --git a/crates/test-programs/wasi-tests/src/bin/path_open_missing.rs b/crates/test-programs/wasi-tests/src/bin/path_open_missing.rs
index 8074090b8..3f56f8d75 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_open_missing.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_open_missing.rs
@@ -7,8 +7,7 @@ unsafe fn test_path_open_missing(dir_fd: wasi::Fd) {
             dir_fd, 0, "file", 0, // not passing O_CREAT here
             0, 0, 0,
         )
-        .expect_err("trying to open a file that doesn't exist")
-        .raw_error(),
+        .expect_err("trying to open a file that doesn't exist"),
         wasi::ERRNO_NOENT
     );
 }
diff --git a/crates/test-programs/wasi-tests/src/bin/path_open_read_without_rights.rs b/crates/test-programs/wasi-tests/src/bin/path_open_read_without_rights.rs
index 0adceee9a..5cab9a033 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_open_read_without_rights.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_open_read_without_rights.rs
@@ -27,9 +27,7 @@ unsafe fn try_read_file(dir_fd: wasi::Fd) {
     // Since we no longer have the right to fd_read, trying to read a file
     // should be an error.
     assert_errno!(
-        wasi::fd_read(fd, &[iovec])
-            .expect_err("reading bytes from file should fail")
-            .raw_error(),
+        wasi::fd_read(fd, &[iovec]).expect_err("reading bytes from file should fail"),
         wasi::ERRNO_BADF
     );
 }
diff --git a/crates/test-programs/wasi-tests/src/bin/path_rename.rs b/crates/test-programs/wasi-tests/src/bin/path_rename.rs
index 8c496d9c9..325a8a390 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_rename.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_rename.rs
@@ -12,8 +12,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
     // Check that source directory doesn't exist anymore
     assert_errno!(
         wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
-            .expect_err("opening a nonexistent path as a directory should fail")
-            .raw_error(),
+            .expect_err("opening a nonexistent path as a directory should fail"),
         wasi::ERRNO_NOENT
     );
 
@@ -41,8 +40,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
         // Check that source directory doesn't exist anymore
         assert_errno!(
             wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
-                .expect_err("opening a nonexistent path as a directory")
-                .raw_error(),
+                .expect_err("opening a nonexistent path as a directory"),
             wasi::ERRNO_NOENT
         );
 
@@ -72,8 +70,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_rename(dir_fd, "source", dir_fd, "target")
-            .expect_err("renaming directory to a nonempty directory")
-            .raw_error(),
+            .expect_err("renaming directory to a nonempty directory"),
         windows => wasi::ERRNO_ACCES,
         unix => wasi::ERRNO_NOTEMPTY
     );
@@ -85,8 +82,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
         // Try renaming dir to a file
         assert_errno!(
             wasi::path_rename(dir_fd, "source", dir_fd, "target/file")
-                .expect_err("renaming a directory to a file")
-                .raw_error(),
+                .expect_err("renaming a directory to a file"),
             wasi::ERRNO_NOTDIR
         );
         wasi::path_unlink_file(dir_fd, "target/file").expect("removing a file");
@@ -107,8 +103,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
     // Check that source file doesn't exist anymore
     assert_errno!(
         wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
-            .expect_err("opening a nonexistent path should fail")
-            .raw_error(),
+            .expect_err("opening a nonexistent path should fail"),
         wasi::ERRNO_NOENT
     );
 
@@ -131,9 +126,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
 
     // Check that source file doesn't exist anymore
     assert_errno!(
-        wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0)
-            .expect_err("opening a nonexistent path")
-            .raw_error(),
+        wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0).expect_err("opening a nonexistent path"),
         wasi::ERRNO_NOENT
     );
 
@@ -153,8 +146,7 @@ unsafe fn test_path_rename(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_rename(dir_fd, "source", dir_fd, "target")
-            .expect_err("renaming a file to existing directory should fail")
-            .raw_error(),
+            .expect_err("renaming a file to existing directory should fail"),
         windows => wasi::ERRNO_ACCES,
         unix => wasi::ERRNO_ISDIR
     );
diff --git a/crates/test-programs/wasi-tests/src/bin/path_rename_file_trailing_slashes.rs b/crates/test-programs/wasi-tests/src/bin/path_rename_file_trailing_slashes.rs
index b884a84d0..853b1d407 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_rename_file_trailing_slashes.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_rename_file_trailing_slashes.rs
@@ -11,20 +11,19 @@ unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_rename(dir_fd, "source/", dir_fd, "target")
-            .expect_err("renaming a file with a trailing slash in the source name should fail")
-            .raw_error(),
+            .expect_err("renaming a file with a trailing slash in the source name should fail"),
         wasi::ERRNO_NOTDIR
     );
     assert_errno!(
-        wasi::path_rename(dir_fd, "source", dir_fd, "target/")
-            .expect_err("renaming a file with a trailing slash in the destination name should fail")
-            .raw_error(),
+        wasi::path_rename(dir_fd, "source", dir_fd, "target/").expect_err(
+            "renaming a file with a trailing slash in the destination name should fail"
+        ),
         wasi::ERRNO_NOTDIR
     );
     assert_errno!(
-        wasi::path_rename(dir_fd, "source/", dir_fd, "target/")
-            .expect_err("renaming a file with a trailing slash in the source and destination names should fail")
-            .raw_error(),
+        wasi::path_rename(dir_fd, "source/", dir_fd, "target/").expect_err(
+            "renaming a file with a trailing slash in the source and destination names should fail"
+        ),
         wasi::ERRNO_NOTDIR
     );
     wasi::path_unlink_file(dir_fd, "source").expect("removing a file");
diff --git a/crates/test-programs/wasi-tests/src/bin/path_symlink_trailing_slashes.rs b/crates/test-programs/wasi-tests/src/bin/path_symlink_trailing_slashes.rs
index f814303d7..f27ced633 100644
--- a/crates/test-programs/wasi-tests/src/bin/path_symlink_trailing_slashes.rs
+++ b/crates/test-programs/wasi-tests/src/bin/path_symlink_trailing_slashes.rs
@@ -6,8 +6,7 @@ unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
         // Dangling symlink: Link destination shouldn't end with a slash.
         assert_errno!(
             wasi::path_symlink("source", dir_fd, "target/")
-                .expect_err("link destination ending with a slash should fail")
-                .raw_error(),
+                .expect_err("link destination ending with a slash should fail"),
             wasi::ERRNO_NOENT
         );
 
@@ -21,8 +20,7 @@ unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
     wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
     assert_errno!(
         wasi::path_symlink("source", dir_fd, "target/")
-            .expect_err("link destination already exists")
-            .raw_error(),
+            .expect_err("link destination already exists"),
         unix => wasi::ERRNO_EXIST,
         windows => wasi::ERRNO_NOENT
     );
@@ -32,8 +30,7 @@ unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
     wasi::path_create_directory(dir_fd, "target").expect("creating a directory");
     assert_errno!(
         wasi::path_symlink("source", dir_fd, "target")
-            .expect_err("link destination already exists")
-            .raw_error(),
+            .expect_err("link destination already exists"),
         unix => wasi::ERRNO_EXIST,
         windows => wasi::ERRNO_NOENT
     );
@@ -44,8 +41,7 @@ unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_symlink("source", dir_fd, "target/")
-            .expect_err("link destination already exists")
-            .raw_error(),
+            .expect_err("link destination already exists"),
         unix => wasi::ERRNO_NOTDIR,
         windows => wasi::ERRNO_NOENT
     );
@@ -56,8 +52,7 @@ unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) {
 
     assert_errno!(
         wasi::path_symlink("source", dir_fd, "target")
-            .expect_err("link destination already exists")
-            .raw_error(),
+            .expect_err("link destination already exists"),
         unix => wasi::ERRNO_EXIST,
         windows => wasi::ERRNO_NOENT
     );
diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs
index f84c7ec4a..cbf980313 100644
--- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs
+++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs
@@ -3,7 +3,7 @@ use wasi_tests::{assert_errno, open_scratch_directory};
 
 const CLOCK_ID: wasi::Userdata = 0x0123_45678;
 
-unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Error> {
+unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Errno> {
     let mut out: Vec<wasi::Event> = Vec::new();
     out.resize_with(r#in.len(), || {
         MaybeUninit::<wasi::Event>::zeroed().assume_init()
@@ -17,7 +17,7 @@ unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Even
 /// seen their events occur.
 unsafe fn poll_oneoff_with_retry(
     r#in: &[wasi::Subscription],
-) -> Result<Vec<wasi::Event>, wasi::Error> {
+) -> Result<Vec<wasi::Event>, wasi::Errno> {
     let mut subscriptions = r#in.to_vec();
     let mut events = Vec::new();
     while !subscriptions.is_empty() {
@@ -47,8 +47,7 @@ unsafe fn test_empty_poll() {
     let mut out: Vec<wasi::Event> = Vec::new();
     assert_errno!(
         wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
-            .expect_err("empty poll_oneoff should fail")
-            .raw_error(),
+            .expect_err("empty poll_oneoff should fail"),
         wasi::ERRNO_INVAL
     );
 }
@@ -64,7 +63,7 @@ unsafe fn test_timeout() {
     let r#in = [wasi::Subscription {
         userdata: CLOCK_ID,
         u: wasi::SubscriptionU {
-            tag: wasi::EVENTTYPE_CLOCK,
+            tag: wasi::EVENTTYPE_CLOCK.raw(),
             u: wasi::SubscriptionUU { clock },
         },
     }];
@@ -75,7 +74,7 @@ unsafe fn test_timeout() {
     let event = &out[0];
     assert_errno!(event.error, wasi::ERRNO_SUCCESS);
     assert_eq!(
-        event.r#type,
+        event.type_,
         wasi::EVENTTYPE_CLOCK,
         "the event.type should equal clock"
     );
@@ -102,7 +101,7 @@ unsafe fn test_sleep() {
     let r#in = [wasi::Subscription {
         userdata: CLOCK_ID,
         u: wasi::SubscriptionU {
-            tag: wasi::EVENTTYPE_CLOCK,
+            tag: wasi::EVENTTYPE_CLOCK.raw(),
             u: wasi::SubscriptionUU { clock },
         },
     }];
@@ -113,7 +112,7 @@ unsafe fn test_sleep() {
     let event = &out[0];
     assert_errno!(event.error, wasi::ERRNO_SUCCESS);
     assert_eq!(
-        event.r#type,
+        event.type_,
         wasi::EVENTTYPE_CLOCK,
         "the event.type should equal clock"
     );
@@ -132,7 +131,7 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_
         wasi::Subscription {
             userdata: 1,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_READ,
+                tag: wasi::EVENTTYPE_FD_READ.raw(),
                 u: wasi::SubscriptionUU {
                     fd_read: wasi::SubscriptionFdReadwrite {
                         file_descriptor: readable_fd,
@@ -143,7 +142,7 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_
         wasi::Subscription {
             userdata: 2,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_WRITE,
+                tag: wasi::EVENTTYPE_FD_WRITE.raw(),
                 u: wasi::SubscriptionUU {
                     fd_write: wasi::SubscriptionFdReadwrite {
                         file_descriptor: writable_fd,
@@ -160,7 +159,7 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_
     );
     assert_errno!(out[0].error, error_code);
     assert_eq!(
-        out[0].r#type,
+        out[0].type_,
         wasi::EVENTTYPE_FD_READ,
         "the event.type_ should equal FD_READ"
     );
@@ -170,7 +169,7 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_
     );
     assert_errno!(out[1].error, error_code);
     assert_eq!(
-        out[1].r#type,
+        out[1].type_,
         wasi::EVENTTYPE_FD_WRITE,
         "the event.type_ should equal FD_WRITE"
     );
@@ -244,7 +243,7 @@ unsafe fn test_fd_readwrite_invalid_fd() {
         wasi::Subscription {
             userdata: 1,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_READ,
+                tag: wasi::EVENTTYPE_FD_READ.raw(),
                 u: wasi::SubscriptionUU {
                     fd_read: fd_readwrite,
                 },
@@ -253,7 +252,7 @@ unsafe fn test_fd_readwrite_invalid_fd() {
         wasi::Subscription {
             userdata: 2,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_WRITE,
+                tag: wasi::EVENTTYPE_FD_WRITE.raw(),
                 u: wasi::SubscriptionUU {
                     fd_write: fd_readwrite,
                 },
@@ -261,7 +260,7 @@ unsafe fn test_fd_readwrite_invalid_fd() {
         },
     ];
     let err = poll_oneoff_impl(&r#in).unwrap_err();
-    assert_eq!(err.raw_error(), wasi::ERRNO_BADF)
+    assert_eq!(err, wasi::ERRNO_BADF)
 }
 
 unsafe fn test_poll_oneoff(dir_fd: wasi::Fd) {
diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs
index 8b2929b27..d3b39c95a 100644
--- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs
+++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs
@@ -6,7 +6,7 @@ const TIMEOUT: u64 = 200_000_000u64; // 200 milliseconds, required to satisfy sl
 const CLOCK_ID: wasi::Userdata = 0x0123_45678;
 const STDIN_ID: wasi::Userdata = 0x8765_43210;
 
-unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Error> {
+unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result<Vec<wasi::Event>, wasi::Errno> {
     let mut out: Vec<wasi::Event> = Vec::new();
     out.resize_with(r#in.len(), || {
         MaybeUninit::<wasi::Event>::zeroed().assume_init()
@@ -31,14 +31,14 @@ unsafe fn test_stdin_read() {
         wasi::Subscription {
             userdata: CLOCK_ID,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_CLOCK,
+                tag: wasi::EVENTTYPE_CLOCK.raw(),
                 u: wasi::SubscriptionUU { clock },
             },
         },
         wasi::Subscription {
             userdata: STDIN_ID,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_READ,
+                tag: wasi::EVENTTYPE_FD_READ.raw(),
                 u: wasi::SubscriptionUU {
                     fd_read: fd_readwrite,
                 },
@@ -50,20 +50,20 @@ unsafe fn test_stdin_read() {
     // Both are valid behaviors that depend on the test environment.
     assert!(out.len() >= 1, "stdin read should return at least 1 event");
     for event in out {
-        if event.r#type == wasi::EVENTTYPE_CLOCK {
+        if event.type_ == wasi::EVENTTYPE_CLOCK {
             assert_errno!(event.error, wasi::ERRNO_SUCCESS);
             assert_eq!(
                 event.userdata, CLOCK_ID,
                 "the event.userdata should contain CLOCK_ID",
             );
-        } else if event.r#type == wasi::EVENTTYPE_FD_READ {
+        } else if event.type_ == wasi::EVENTTYPE_FD_READ {
             assert_errno!(event.error, wasi::ERRNO_SUCCESS);
             assert_eq!(
                 event.userdata, STDIN_ID,
                 "the event.userdata should contain STDIN_ID",
             );
         } else {
-            panic!("unexpected event type {}", event.r#type);
+            panic!("unexpected event type {}", event.type_.raw());
         }
     }
 }
@@ -74,7 +74,7 @@ fn writable_subs(h: &HashMap<u64, wasi::Fd>) -> Vec<wasi::Subscription> {
         .map(|(ud, fd)| wasi::Subscription {
             userdata: *ud,
             u: wasi::SubscriptionU {
-                tag: wasi::EVENTTYPE_FD_WRITE,
+                tag: wasi::EVENTTYPE_FD_WRITE.raw(),
                 u: wasi::SubscriptionUU {
                     fd_write: wasi::SubscriptionFdReadwrite {
                         file_descriptor: *fd,
@@ -92,7 +92,7 @@ unsafe fn test_stdout_stderr_write() {
     let clock = wasi::Subscription {
         userdata: CLOCK_ID,
         u: wasi::SubscriptionU {
-            tag: wasi::EVENTTYPE_CLOCK,
+            tag: wasi::EVENTTYPE_CLOCK.raw(),
             u: wasi::SubscriptionUU {
                 clock: wasi::SubscriptionClock {
                     id: wasi::CLOCKID_MONOTONIC,
@@ -114,7 +114,7 @@ unsafe fn test_stdout_stderr_write() {
                 }
                 ud => {
                     if let Some(_) = writable.remove(&ud) {
-                        assert_eq!(event.r#type, wasi::EVENTTYPE_FD_WRITE);
+                        assert_eq!(event.type_, wasi::EVENTTYPE_FD_WRITE);
                         assert_errno!(event.error, wasi::ERRNO_SUCCESS);
                     } else {
                         panic!("Unknown userdata {}, pending sub: {:?}", ud, writable)
diff --git a/crates/test-programs/wasi-tests/src/bin/readlink.rs b/crates/test-programs/wasi-tests/src/bin/readlink.rs
index 81fa33188..b06c01c59 100644
--- a/crates/test-programs/wasi-tests/src/bin/readlink.rs
+++ b/crates/test-programs/wasi-tests/src/bin/readlink.rs
@@ -25,7 +25,7 @@ unsafe fn test_readlink(dir_fd: wasi::Fd) {
     let err = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len())
         .err()
         .expect("readlink with too-small buffer should fail");
-    assert_errno!(err.raw_error(), wasi::ERRNO_RANGE);
+    assert_errno!(err, wasi::ERRNO_RANGE);
 
     // Clean up.
     wasi::path_unlink_file(dir_fd, "target").expect("removing a file");
diff --git a/crates/test-programs/wasi-tests/src/bin/remove_directory_trailing_slashes.rs b/crates/test-programs/wasi-tests/src/bin/remove_directory_trailing_slashes.rs
index e3805a96e..9628c7ad5 100644
--- a/crates/test-programs/wasi-tests/src/bin/remove_directory_trailing_slashes.rs
+++ b/crates/test-programs/wasi-tests/src/bin/remove_directory_trailing_slashes.rs
@@ -21,16 +21,14 @@ unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
     // Test that removing it with no trailing slash fails.
     assert_errno!(
         wasi::path_remove_directory(dir_fd, "file")
-            .expect_err("remove_directory without a trailing slash on a file should fail")
-            .raw_error(),
+            .expect_err("remove_directory without a trailing slash on a file should fail"),
         wasi::ERRNO_NOTDIR
     );
 
     // Test that removing it with a trailing slash fails.
     assert_errno!(
         wasi::path_remove_directory(dir_fd, "file/")
-            .expect_err("remove_directory with a trailing slash on a file should fail")
-            .raw_error(),
+            .expect_err("remove_directory with a trailing slash on a file should fail"),
         unix => wasi::ERRNO_NOTDIR,
         windows => wasi::ERRNO_NOENT
     );
diff --git a/crates/test-programs/wasi-tests/src/bin/remove_nonempty_directory.rs b/crates/test-programs/wasi-tests/src/bin/remove_nonempty_directory.rs
index 19488e748..6732ba537 100644
--- a/crates/test-programs/wasi-tests/src/bin/remove_nonempty_directory.rs
+++ b/crates/test-programs/wasi-tests/src/bin/remove_nonempty_directory.rs
@@ -11,8 +11,7 @@ unsafe fn test_remove_nonempty_directory(dir_fd: wasi::Fd) {
     // Test that attempting to unlink the first directory returns the expected error code.
     assert_errno!(
         wasi::path_remove_directory(dir_fd, "dir")
-            .expect_err("remove_directory on a directory should return ENOTEMPTY")
-            .raw_error(),
+            .expect_err("remove_directory on a directory should return ENOTEMPTY"),
         wasi::ERRNO_NOTEMPTY
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/renumber.rs b/crates/test-programs/wasi-tests/src/bin/renumber.rs
index 7867a5401..1a50f02ad 100644
--- a/crates/test-programs/wasi-tests/src/bin/renumber.rs
+++ b/crates/test-programs/wasi-tests/src/bin/renumber.rs
@@ -47,9 +47,7 @@ unsafe fn test_renumber(dir_fd: wasi::Fd) {
 
     // Ensure that fd_from is closed
     assert_errno!(
-        wasi::fd_close(fd_from)
-            .expect_err("closing already closed file descriptor")
-            .raw_error(),
+        wasi::fd_close(fd_from).expect_err("closing already closed file descriptor"),
         wasi::ERRNO_BADF
     );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/symlink_loop.rs b/crates/test-programs/wasi-tests/src/bin/symlink_loop.rs
index c14356f89..1c3226adb 100644
--- a/crates/test-programs/wasi-tests/src/bin/symlink_loop.rs
+++ b/crates/test-programs/wasi-tests/src/bin/symlink_loop.rs
@@ -9,8 +9,7 @@ unsafe fn test_symlink_loop(dir_fd: wasi::Fd) {
         // Try to open it.
         assert_errno!(
             wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0)
-                .expect_err("opening a self-referencing symlink")
-                .raw_error(),
+                .expect_err("opening a self-referencing symlink"),
             wasi::ERRNO_LOOP
         );
 
diff --git a/crates/test-programs/wasi-tests/src/bin/truncation_rights.rs b/crates/test-programs/wasi-tests/src/bin/truncation_rights.rs
index 5faa85fd9..0480d74b9 100644
--- a/crates/test-programs/wasi-tests/src/bin/truncation_rights.rs
+++ b/crates/test-programs/wasi-tests/src/bin/truncation_rights.rs
@@ -66,8 +66,7 @@ unsafe fn test_truncation_rights(dir_fd: wasi::Fd) {
         // wasi_unstable::RIGHT_PATH_FILESTAT_SET_SIZE right.
         assert_errno!(
             wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0)
-                .expect_err("truncating a file without path_filestat_set_size right")
-                .raw_error(),
+                .expect_err("truncating a file without path_filestat_set_size right"),
             wasi::ERRNO_PERM
         );
     }
diff --git a/crates/test-programs/wasi-tests/src/bin/unlink_file_trailing_slashes.rs b/crates/test-programs/wasi-tests/src/bin/unlink_file_trailing_slashes.rs
index 519ef230f..bedecd9aa 100644
--- a/crates/test-programs/wasi-tests/src/bin/unlink_file_trailing_slashes.rs
+++ b/crates/test-programs/wasi-tests/src/bin/unlink_file_trailing_slashes.rs
@@ -8,8 +8,7 @@ unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
     // Test that unlinking it fails.
     assert_errno!(
         wasi::path_unlink_file(dir_fd, "dir")
-            .expect_err("unlink_file on a directory should fail")
-            .raw_error(),
+            .expect_err("unlink_file on a directory should fail"),
         macos => wasi::ERRNO_PERM,
         unix => wasi::ERRNO_ISDIR,
         windows => wasi::ERRNO_ACCES
@@ -18,8 +17,7 @@ unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
     // Test that unlinking it with a trailing flash fails.
     assert_errno!(
         wasi::path_unlink_file(dir_fd, "dir/")
-            .expect_err("unlink_file on a directory should fail")
-            .raw_error(),
+            .expect_err("unlink_file on a directory should fail"),
         macos => wasi::ERRNO_PERM,
         unix => wasi::ERRNO_ISDIR,
         windows => wasi::ERRNO_ACCES
@@ -34,8 +32,7 @@ unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) {
     // Test that unlinking it with a trailing flash fails.
     assert_errno!(
         wasi::path_unlink_file(dir_fd, "file/")
-            .expect_err("unlink_file with a trailing slash should fail")
-            .raw_error(),
+            .expect_err("unlink_file with a trailing slash should fail"),
         unix => wasi::ERRNO_NOTDIR,
         windows => wasi::ERRNO_NOENT
     );
diff --git a/crates/test-programs/wasi-tests/src/lib.rs b/crates/test-programs/wasi-tests/src/lib.rs
index bddd7c3ca..da6faf00e 100644
--- a/crates/test-programs/wasi-tests/src/lib.rs
+++ b/crates/test-programs/wasi-tests/src/lib.rs
@@ -18,7 +18,7 @@ pub fn open_scratch_directory(path: &str) -> Result<wasi::Fd, String> {
                 Ok(s) => s,
                 Err(_) => break,
             };
-            if stat.tag != wasi::PREOPENTYPE_DIR {
+            if stat.tag != wasi::PREOPENTYPE_DIR.raw() {
                 continue;
             }
             let mut dst = Vec::with_capacity(stat.u.dir.pr_name_len);
@@ -122,8 +122,8 @@ macro_rules! assert_errno {
             }
             assert!( $( e == $i || )+ false,
                 "expected errno {}; got {}",
-                Alt(&[ $( wasi::errno_name($i) ),+ ]),
-                wasi::errno_name(e),
+                Alt(&[ $( $i.name() ),+ ]),
+                e.name()
             )
         }
     };
diff --git a/tests/all/linker.rs b/tests/all/linker.rs
index 1f65f017b..0f1c38c6c 100644
--- a/tests/all/linker.rs
+++ b/tests/all/linker.rs
@@ -23,6 +23,24 @@ fn link_undefined() -> Result<()> {
     Ok(())
 }
 
+#[test]
+fn test_unknown_import_error() -> Result<()> {
+    let mut store = Store::<()>::default();
+    let linker = Linker::new(store.engine());
+    let module = Module::new(
+        store.engine(),
+        r#"(module (import "unknown-module" "unknown-name" (func)))"#,
+    )?;
+    let err = linker
+        .instantiate(&mut store, &module)
+        .expect_err("should fail");
+    let unknown_import: UnknownImportError = err.downcast()?;
+    assert_eq!(unknown_import.module(), "unknown-module");
+    assert_eq!(unknown_import.name(), "unknown-name");
+    unknown_import.ty().unwrap_func();
+    Ok(())
+}
+
 #[test]
 fn link_twice_bad() -> Result<()> {
     let mut store = Store::<()>::default();
@@ -366,7 +384,8 @@ fn test_trapping_unknown_import() -> Result<()> {
         .get_func(&mut store, "run")
         .expect("expected a run func in the module");
 
-    assert!(run_func.call(&mut store, &[], &mut []).is_err());
+    let err = run_func.call(&mut store, &[], &mut []).unwrap_err();
+    assert!(err.is::<UnknownImportError>());
 
     // "other" does not call the import function, so it should not trap
     let other_func = instance