Repository: bytecodealliance/wasmtime
Pull request #5746 (2023-02-08 01:37:25)

Cranelift: Generalize `(x << k) >> k` optimization


Patch:
diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle
index 60673fc41..ca27ac8c5 100644
--- a/cranelift/codegen/src/isa/riscv64/inst.isle
+++ b/cranelift/codegen/src/isa/riscv64/inst.isle
@@ -205,11 +205,6 @@
       (x ValueRegs)
       (y ValueRegs))
 
-    (ReferenceCheck
-      (rd WritableReg)
-      (op ReferenceCheckOP)
-      (x Reg))
-
     (BrTable
       (index Reg)
       (tmp1 WritableReg)
@@ -370,11 +365,6 @@
   (Umin)
 ))
 
-(type ReferenceCheckOP (enum
-  (IsNull)
-  (IsInvalid)
-))
-
 (type AtomicOP (enum
   (LrW)
   (ScW)
@@ -879,6 +869,17 @@
 (rule (select_addi (fits_in_64 ty)) (AluOPRRI.Addi))
 
 
+;; Helper for emiting the `sltiu` instruction
+(decl sltiu (Reg Imm12) Reg)
+(rule (sltiu r imm)
+  (alu_rr_imm12 (AluOPRRI.SltiU) r imm))
+
+;; Helper for emiting the `seqz` mnemonic
+(decl seqz (Reg) Reg)
+(rule (seqz r)
+  (sltiu r (imm12_const 1)))
+
+
 (decl bnot_128 (ValueRegs) ValueRegs)
 (rule
   (bnot_128 val)
@@ -1699,14 +1700,6 @@
 (decl gen_moves (ValueRegs Type Type) ValueRegs)
 (extern constructor gen_moves gen_moves)
 
-;;
-(decl gen_reference_check (ReferenceCheckOP Reg) Reg)
-(rule
-  (gen_reference_check op r)
-  (let
-    ((tmp WritableReg (temp_writable_reg $I64))
-      (_ Unit (emit (MInst.ReferenceCheck tmp op r))))
-    tmp))
 
 ;;
 (decl gen_select (Type Reg ValueRegs ValueRegs) ValueRegs)
diff --git a/cranelift/codegen/src/isa/riscv64/inst/args.rs b/cranelift/codegen/src/isa/riscv64/inst/args.rs
index 4d682e5a1..aec9b499c 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/args.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/args.rs
@@ -1639,23 +1639,6 @@ impl IntSelectOP {
     }
 }
 
-impl ReferenceCheckOP {
-    pub(crate) fn op_name(self) -> &'static str {
-        match self {
-            ReferenceCheckOP::IsNull => "is_null",
-            ReferenceCheckOP::IsInvalid => "is_invalid",
-        }
-    }
-    #[inline]
-    pub(crate) fn from_ir_op(op: crate::ir::Opcode) -> Self {
-        match op {
-            crate::ir::Opcode::IsInvalid => Self::IsInvalid,
-            crate::ir::Opcode::IsNull => Self::IsNull,
-            _ => unreachable!(),
-        }
-    }
-}
-
 #[derive(Clone, Copy)]
 pub enum CsrAddress {
     Fcsr = 0x3,
diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
index b85ee23b3..007a2c38f 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs
@@ -680,57 +680,6 @@ impl MachInstEmit for Inst {
                         .for_each(|inst| inst.emit(&[], sink, emit_info, state));
                 }
             }
-
-            &Inst::ReferenceCheck { rd, op, x } => {
-                let x = allocs.next(x);
-                let rd = allocs.next_writable(rd);
-                let mut insts = SmallInstVec::new();
-                match op {
-                    ReferenceCheckOP::IsNull => {
-                        insts.push(Inst::CondBr {
-                            taken: BranchTarget::ResolvedOffset(Inst::INSTRUCTION_SIZE * 3),
-                            not_taken: BranchTarget::zero(),
-                            kind: IntegerCompare {
-                                kind: IntCC::Equal,
-                                rs1: zero_reg(),
-                                rs2: x,
-                            },
-                        });
-                        // here is false
-                        insts.push(Inst::load_imm12(rd, Imm12::FALSE));
-                        insts.push(Inst::Jal {
-                            dest: BranchTarget::ResolvedOffset(Inst::INSTRUCTION_SIZE * 2),
-                        });
-                        // here is true
-                        insts.push(Inst::load_imm12(rd, Imm12::TRUE));
-                    }
-
-                    ReferenceCheckOP::IsInvalid => {
-                        // todo:: right now just check if it is null
-                        // null is a valid reference??????
-                        insts.push(Inst::CondBr {
-                            taken: BranchTarget::ResolvedOffset(Inst::INSTRUCTION_SIZE * 3),
-                            not_taken: BranchTarget::zero(),
-                            kind: IntegerCompare {
-                                kind: IntCC::Equal,
-                                rs1: zero_reg(),
-                                rs2: x,
-                            },
-                        });
-                        // here is false
-                        insts.push(Inst::load_imm12(rd, Imm12::FALSE));
-                        insts.push(Inst::Jal {
-                            dest: BranchTarget::ResolvedOffset(Inst::INSTRUCTION_SIZE * 2),
-                        });
-                        // here is true
-                        insts.push(Inst::load_imm12(rd, Imm12::TRUE));
-                    }
-                }
-
-                insts
-                    .into_iter()
-                    .for_each(|i| i.emit(&[], sink, emit_info, state));
-            }
             &Inst::Args { .. } => {
                 // Nothing: this is a pseudoinstruction that serves
                 // only to constrain registers at a certain point.
@@ -1802,23 +1751,23 @@ impl MachInstEmit for Inst {
                 if out_type.bits() < 32 && is_signed {
                     // load value part mask.
                     Inst::load_constant_u32(
-                        tmp,
+                        writable_spilltmp_reg(),
                         if 16 == out_type.bits() {
                             (u16::MAX >> 1) as u64
                         } else {
                             // I8
                             (u8::MAX >> 1) as u64
                         },
-                        &mut |_| writable_spilltmp_reg(),
+                        &mut |_| writable_spilltmp_reg2(),
                     )
                     .into_iter()
                     .for_each(|x| x.emit(&[], sink, emit_info, state));
                     // keep value part.
                     Inst::AluRRR {
                         alu_op: AluOPRRR::And,
-                        rd: tmp,
+                        rd: writable_spilltmp_reg(),
                         rs1: rd.to_reg(),
-                        rs2: tmp.to_reg(),
+                        rs2: spilltmp_reg(),
                     }
                     .emit(&[], sink, emit_info, state);
                     // extact sign bit.
@@ -1846,7 +1795,7 @@ impl MachInstEmit for Inst {
                         alu_op: AluOPRRR::Or,
                         rd: rd,
                         rs1: rd.to_reg(),
-                        rs2: tmp.to_reg(),
+                        rs2: spilltmp_reg(),
                     }
                     .emit(&[], sink, emit_info, state);
                 }
diff --git a/cranelift/codegen/src/isa/riscv64/inst/mod.rs b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
index 8f59b7676..d7fbb6ca9 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/mod.rs
@@ -51,8 +51,7 @@ pub(crate) type VecWritableReg = Vec<Writable<Reg>>;
 use crate::isa::riscv64::lower::isle::generated_code::MInst;
 pub use crate::isa::riscv64::lower::isle::generated_code::{
     AluOPRRI, AluOPRRR, AtomicOP, CsrOP, FClassResult, FFlagsException, FenceFm, FloatRoundOP,
-    FloatSelectOP, FpuOPRR, FpuOPRRR, FpuOPRRRR, IntSelectOP, LoadOP, MInst as Inst,
-    ReferenceCheckOP, StoreOP, FRM,
+    FloatSelectOP, FpuOPRR, FpuOPRRR, FpuOPRRRR, IntSelectOP, LoadOP, MInst as Inst, StoreOP, FRM,
 };
 
 type BoxCallInfo = Box<CallInfo>;
@@ -471,10 +470,6 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
                 collector.reg_early_def(d.clone());
             }
         }
-        &Inst::ReferenceCheck { rd, x, .. } => {
-            collector.reg_use(x);
-            collector.reg_def(rd);
-        }
         &Inst::AtomicCas {
             offset,
             t0,
@@ -529,7 +524,7 @@ fn riscv64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut Operan
         &Inst::FcvtToInt { rd, rs, tmp, .. } => {
             collector.reg_use(rs);
             collector.reg_early_def(tmp);
-            collector.reg_def(rd);
+            collector.reg_early_def(rd);
         }
         &Inst::SelectIf {
             ref rd,
@@ -1185,12 +1180,6 @@ impl Inst {
                     imm.bits
                 )
             }
-
-            &Inst::ReferenceCheck { rd, op, x } => {
-                let x = format_reg(x, allocs);
-                let rd = format_reg(rd.to_reg(), allocs);
-                format!("{} {},{}", op.op_name(), rd, x)
-            }
             &Inst::Jalr { rd, base, offset } => {
                 let base = format_reg(base, allocs);
                 let rd = format_reg(rd.to_reg(), allocs);
@@ -1344,6 +1333,9 @@ impl Inst {
                     (AluOPRRI::Xori, _, imm12) if imm12.as_i16() == -1 => {
                         return format!("not {},{}", rd, rs_s);
                     }
+                    (AluOPRRI::SltiU, _, imm12) if imm12.as_i16() == 1 => {
+                        return format!("seqz {},{}", rd, rs_s);
+                    }
                     (alu_op, _, _) if alu_op.option_funct12().is_some() => {
                         format!("{} {},{}", alu_op.op_name(), rd, rs_s)
                     }
diff --git a/cranelift/codegen/src/isa/riscv64/lower.isle b/cranelift/codegen/src/isa/riscv64/lower.isle
index 944317916..9d1f43af5 100644
--- a/cranelift/codegen/src/isa/riscv64/lower.isle
+++ b/cranelift/codegen/src/isa/riscv64/lower.isle
@@ -612,14 +612,16 @@
   (gen_stack_addr ss offset))
 
 ;;;;;  Rules for `is_null`;;;;;;;;;
-(rule
-  (lower (is_null v))
-  (gen_reference_check (ReferenceCheckOP.IsNull) v))
+
+;; Null references are represented by the constant value `0`.
+(rule (lower (is_null v))
+  (seqz v))
 
 ;;;;;  Rules for `is_invalid`;;;;;;;;;
-(rule
-  (lower (is_invalid v))
-  (gen_reference_check (ReferenceCheckOP.IsInvalid) v))
+
+;; Invalid references are represented by the constant value `-1`.
+(rule (lower (is_invalid v))
+  (seqz (alu_rr_imm12 (AluOPRRI.Addi) v (imm12_const 1))))
 
 ;;;;;  Rules for `select`;;;;;;;;;
 (rule
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index 35bbd5f79..f1b3fed92 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -452,7 +452,7 @@ fn construct_dest<F: std::ops::FnMut(Type) -> WritableReg>(
     mut alloc: F,
     ty: Type,
 ) -> WritableValueRegs {
-    if ty.is_int() {
+    if ty.is_int() || ty.is_ref() {
         if ty.bits() == 128 {
             WritableValueRegs::two(alloc(I64), alloc(I64))
         } else {
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index ff14669e7..dcc8bd1f0 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -2025,7 +2025,7 @@
 
 ;; Rules for `is_invalid` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-;; Null references are represented by the constant value `-1`.
+;; Invalid references are represented by the constant value `-1`.
 (rule (lower (is_invalid src @ (value_type $R64)))
       (with_flags
        (x64_cmp_imm (OperandSize.Size64) 0xffffffff src)  ;; simm32 0xffff_ffff is sign-extended to -1.
diff --git a/cranelift/codegen/src/isle_prelude.rs b/cranelift/codegen/src/isle_prelude.rs
index 0e3e293c1..435955fa2 100644
--- a/cranelift/codegen/src/isle_prelude.rs
+++ b/cranelift/codegen/src/isle_prelude.rs
@@ -129,6 +129,16 @@ macro_rules! isle_common_prelude_methods {
             x == y
         }
 
+        #[inline]
+        fn u64_le(&mut self, x: u64, y: u64) -> bool {
+            x <= y
+        }
+
+        #[inline]
+        fn u64_lt(&mut self, x: u64, y: u64) -> bool {
+            x < y
+        }
+
         #[inline]
         fn u64_is_zero(&mut self, value: u64) -> bool {
             0 == value
diff --git a/cranelift/codegen/src/opts/algebraic.isle b/cranelift/codegen/src/opts/algebraic.isle
index da177603e..ecd71988c 100644
--- a/cranelift/codegen/src/opts/algebraic.isle
+++ b/cranelift/codegen/src/opts/algebraic.isle
@@ -187,16 +187,10 @@
 (rule (simplify (imul ty (iconst _ (imm64_power_of_two c)) x))
       (ishl ty x (iconst ty (imm64 c))))
 
-;; x<<32>>32: uextend/sextend 32->64.
-(rule (simplify (ushr $I64 (ishl $I64 (uextend $I64 x @ (value_type $I32)) (iconst _ (simm32 32))) (iconst _ (simm32 32))))
-      (uextend $I64 x))
-
-(rule (simplify (sshr $I64 (ishl $I64 (uextend $I64 x @ (value_type $I32)) (iconst _ (simm32 32))) (iconst _ (simm32 32))))
-      (sextend $I64 x))
-
 ;; TODO: strength reduction: div to shifts
 ;; TODO: div/rem by constants -> magic multiplications
 
+
 ;; `(x >> k) << k` is the same as masking off the bottom `k` bits (regardless if
 ;; this is a signed or unsigned shift right).
 (rule (simplify (ishl (fits_in_64 ty)
@@ -210,6 +204,66 @@
       (let ((mask Imm64 (imm64_shl ty (imm64 0xFFFF_FFFF_FFFF_FFFF) k)))
         (band ty x (iconst ty mask))))
 
+
+;; For unsigned shifts, `(x << k) >> k` is the same as masking out the top
+;; `k` bits. A similar rule is valid for vectors but this `iconst` mask only
+;; works for scalar integers.
+(rule (simplify (ushr (fits_in_64 (ty_int ty))
+                      (ishl ty x (iconst _ k))
+                      (iconst _ k)))
+      (band ty x (iconst ty (imm64_ushr ty (imm64 (ty_mask ty)) k))))
+
+
+;; For signed shifts, `(x << k) >> k` does sign-extension from `n` bits to
+;; `n+k` bits. In the special case where `x` is the result of either `sextend`
+;; or `uextend` from `n` bits to `n+k` bits, we can implement this using
+;; `sextend`.
+(rule (simplify (sshr wide
+                 (ishl wide
+                  (uextend wide x @ (value_type narrow))
+                  (iconst _ shift))
+                 (iconst _ shift)))
+      (if-let (u64_from_imm64 shift_u64) shift)
+      (if-let $true (u64_eq shift_u64 (u64_sub (ty_bits_u64 wide) (ty_bits_u64 narrow))))
+      (sextend wide x))
+
+;; If `k` is smaller than the difference in bit widths of the two types, then
+;; the intermediate sign bit comes from the extend op, so the final result is
+;; the same as the original extend op.
+(rule (simplify (sshr wide
+                 (ishl wide
+                  x @ (uextend wide (value_type narrow))
+                  (iconst _ shift))
+                 (iconst _ shift)))
+      (if-let (u64_from_imm64 shift_u64) shift)
+      (if-let $true (u64_lt shift_u64 (u64_sub (ty_bits_u64 wide) (ty_bits_u64 narrow))))
+      x)
+
+;; If the original extend op was `sextend`, then both of the above cases say
+;; the result should also be `sextend`.
+(rule (simplify (sshr wide
+                 (ishl wide
+                  x @ (sextend wide (value_type narrow))
+                  (iconst _ shift))
+                 (iconst _ shift)))
+      (if-let (u64_from_imm64 shift_u64) shift)
+      (if-let $true (u64_le shift_u64 (u64_sub (ty_bits_u64 wide) (ty_bits_u64 narrow))))
+      x)
+
+
+;; Masking out any of the top bits of the result of `uextend` is a no-op. (This
+;; is like a cheap version of known-bits analysis.)
+(rule (simplify (band wide x @ (uextend _ (value_type narrow)) (iconst _ (u64_from_imm64 mask))))
+      ; Check that `narrow_mask` has a subset of the bits that `mask` does.
+      (if-let $true (let ((narrow_mask u64 (ty_mask narrow))) (u64_eq narrow_mask (u64_and mask narrow_mask))))
+      x)
+
+;; Masking out the sign-extended bits of an `sextend` turns it into a `uextend`.
+(rule (simplify (band wide (sextend _ x @ (value_type narrow)) (iconst _ (u64_from_imm64 mask))))
+      (if-let $true (u64_eq mask (ty_mask narrow)))
+      (uextend wide x))
+
+
 ;; Rematerialize ALU-op-with-imm and iconsts in each block where they're
 ;; used. This is neutral (add-with-imm) or positive (iconst) for
 ;; register pressure, and these ops are very cheap.
diff --git a/cranelift/codegen/src/prelude.isle b/cranelift/codegen/src/prelude.isle
index 701f414a9..89fc8e981 100644
--- a/cranelift/codegen/src/prelude.isle
+++ b/cranelift/codegen/src/prelude.isle
@@ -144,6 +144,12 @@
 (decl pure u64_eq (u64 u64) bool)
 (extern constructor u64_eq u64_eq)
 
+(decl pure u64_le (u64 u64) bool)
+(extern constructor u64_le u64_le)
+
+(decl pure u64_lt (u64 u64) bool)
+(extern constructor u64_lt u64_lt)
+
 (decl pure i64_sextend_imm64 (Type Imm64) i64)
 (extern constructor i64_sextend_imm64 i64_sextend_imm64)
 
diff --git a/cranelift/interpreter/src/address.rs b/cranelift/interpreter/src/address.rs
index 1d1f83114..97d610d27 100644
--- a/cranelift/interpreter/src/address.rs
+++ b/cranelift/interpreter/src/address.rs
@@ -15,8 +15,8 @@
 //! are the "entry" field, the amount of "entry" bits depends on the size of the address and
 //! the "region" of the address. The remaining bits belong to the "offset" field
 //!
-//! An example address could be a 32 bit address, in the `heap` region, which has 2 "entry" bits
-//! this address would have 32 - 2 - 2 = 28 offset bits.
+//! An example address could be a 32 bit address, in the `function` region, which has 1 "entry" bit
+//! this address would have 32 - 1 - 2 = 29 offset bits.
 //!
 //! The only exception to this is the "stack" region, where, because we only have a single "stack"
 //! we have 0 "entry" bits, and thus is all offset.
@@ -24,11 +24,11 @@
 //! | address size | address kind | region value (2 bits) | entry bits (#) | offset bits (#) |
 //! |--------------|--------------|-----------------------|----------------|-----------------|
 //! | 32           | Stack        | 0b00                  | 0              | 30              |
-//! | 32           | Heap         | 0b01                  | 2              | 28              |
+//! | 32           | Function     | 0b01                  | 1              | 29              |
 //! | 32           | Table        | 0b10                  | 5              | 25              |
 //! | 32           | GlobalValue  | 0b11                  | 6              | 24              |
 //! | 64           | Stack        | 0b00                  | 0              | 62              |
-//! | 64           | Heap         | 0b01                  | 6              | 56              |
+//! | 64           | Function     | 0b01                  | 1              | 61              |
 //! | 64           | Table        | 0b10                  | 10             | 52              |
 //! | 64           | GlobalValue  | 0b11                  | 12             | 50              |
 
@@ -68,7 +68,7 @@ impl TryFrom<Type> for AddressSize {
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum AddressRegion {
     Stack,
-    Heap,
+    Function,
     Table,
     GlobalValue,
 }
@@ -78,7 +78,7 @@ impl AddressRegion {
         assert!(bits < 4);
         match bits {
             0 => AddressRegion::Stack,
-            1 => AddressRegion::Heap,
+            1 => AddressRegion::Function,
             2 => AddressRegion::Table,
             3 => AddressRegion::GlobalValue,
             _ => unreachable!(),
@@ -88,7 +88,7 @@ impl AddressRegion {
     pub fn encode(self) -> u64 {
         match self {
             AddressRegion::Stack => 0,
-            AddressRegion::Heap => 1,
+            AddressRegion::Function => 1,
             AddressRegion::Table => 2,
             AddressRegion::GlobalValue => 3,
         }
@@ -143,11 +143,13 @@ impl Address {
             // We only have one stack, so the whole address is offset
             (_, AddressRegion::Stack) => 0,
 
-            (AddressSize::_32, AddressRegion::Heap) => 2,
+            // We have two function "entries", one for libcalls, and
+            // another for user functions.
+            (_, AddressRegion::Function) => 1,
+
             (AddressSize::_32, AddressRegion::Table) => 5,
             (AddressSize::_32, AddressRegion::GlobalValue) => 6,
 
-            (AddressSize::_64, AddressRegion::Heap) => 6,
             (AddressSize::_64, AddressRegion::Table) => 10,
             (AddressSize::_64, AddressRegion::GlobalValue) => 12,
         }
@@ -224,6 +226,22 @@ impl TryFrom<u64> for Address {
     }
 }
 
+#[derive(Debug, Clone, PartialEq)]
+pub enum AddressFunctionEntry {
+    UserFunction = 0,
+    LibCall,
+}
+
+impl From<u64> for AddressFunctionEntry {
+    fn from(bits: u64) -> Self {
+        match bits {
+            0 => AddressFunctionEntry::UserFunction,
+            1 => AddressFunctionEntry::LibCall,
+            _ => unreachable!(),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -233,7 +251,7 @@ mod tests {
     fn address_region_roundtrip_encode_decode() {
         let all_regions = [
             AddressRegion::Stack,
-            AddressRegion::Heap,
+            AddressRegion::Function,
             AddressRegion::Table,
             AddressRegion::GlobalValue,
         ];
@@ -250,10 +268,10 @@ mod tests {
             (AddressSize::_32, AddressRegion::Stack, 0, 1),
             (AddressSize::_32, AddressRegion::Stack, 0, 1024),
             (AddressSize::_32, AddressRegion::Stack, 0, 0x3FFF_FFFF),
-            (AddressSize::_32, AddressRegion::Heap, 0, 0),
-            (AddressSize::_32, AddressRegion::Heap, 1, 1),
-            (AddressSize::_32, AddressRegion::Heap, 3, 1024),
-            (AddressSize::_32, AddressRegion::Heap, 3, 0x0FFF_FFFF),
+            (AddressSize::_32, AddressRegion::Function, 0, 0),
+            (AddressSize::_32, AddressRegion::Function, 1, 1),
+            (AddressSize::_32, AddressRegion::Function, 0, 1024),
+            (AddressSize::_32, AddressRegion::Function, 1, 0x0FFF_FFFF),
             (AddressSize::_32, AddressRegion::Table, 0, 0),
             (AddressSize::_32, AddressRegion::Table, 1, 1),
             (AddressSize::_32, AddressRegion::Table, 31, 0x1FF_FFFF),
@@ -268,10 +286,10 @@ mod tests {
                 0,
                 0x3FFFFFFF_FFFFFFFF,
             ),
-            (AddressSize::_64, AddressRegion::Heap, 0, 0),
-            (AddressSize::_64, AddressRegion::Heap, 1, 1),
-            (AddressSize::_64, AddressRegion::Heap, 3, 1024),
-            (AddressSize::_64, AddressRegion::Heap, 3, 0x0FFF_FFFF),
+            (AddressSize::_64, AddressRegion::Function, 0, 0),
+            (AddressSize::_64, AddressRegion::Function, 1, 1),
+            (AddressSize::_64, AddressRegion::Function, 0, 1024),
+            (AddressSize::_64, AddressRegion::Function, 1, 0x0FFF_FFFF),
             (AddressSize::_64, AddressRegion::Table, 0, 0),
             (AddressSize::_64, AddressRegion::Table, 1, 1),
             (AddressSize::_64, AddressRegion::Table, 31, 0x1FF_FFFF),
diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs
index 7a5c00a16..290901dc4 100644
--- a/cranelift/interpreter/src/interpreter.rs
+++ b/cranelift/interpreter/src/interpreter.rs
@@ -2,17 +2,17 @@
 //!
 //! This module partially contains the logic for interpreting Cranelift IR.
 
-use crate::address::{Address, AddressRegion, AddressSize};
+use crate::address::{Address, AddressFunctionEntry, AddressRegion, AddressSize};
 use crate::environment::{FuncIndex, FunctionStore};
 use crate::frame::Frame;
 use crate::instruction::DfgInstructionContext;
-use crate::state::{MemoryError, State};
+use crate::state::{InterpreterFunctionRef, MemoryError, State};
 use crate::step::{step, ControlFlow, StepError};
 use crate::value::{Value, ValueError};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::{
-    ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, LibCall, StackSlot,
-    TrapCode, Type, Value as ValueRef,
+    ArgumentPurpose, Block, ExternalName, FuncRef, Function, GlobalValue, GlobalValueData, LibCall,
+    StackSlot, TrapCode, Type, Value as ValueRef,
 };
 use log::trace;
 use smallvec::SmallVec;
@@ -346,6 +346,63 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
         Ok(v.write_to_slice(dst))
     }
 
+    fn function_address(
+        &self,
+        size: AddressSize,
+        name: &ExternalName,
+    ) -> Result<Address, MemoryError> {
+        let curr_func = self.get_current_function();
+        let (entry, index) = match name {
+            ExternalName::User(username) => {
+                let ext_name = &curr_func.params.user_named_funcs()[*username];
+
+                // TODO: This is not optimal since we are looking up by string name
+                let index = self.functions.index_of(&ext_name.to_string()).unwrap();
+
+                (AddressFunctionEntry::UserFunction, index.as_u32())
+            }
+
+            ExternalName::TestCase(testname) => {
+                // TODO: This is not optimal since we are looking up by string name
+                let index = self.functions.index_of(&testname.to_string()).unwrap();
+
+                (AddressFunctionEntry::UserFunction, index.as_u32())
+            }
+            ExternalName::LibCall(libcall) => {
+                // We don't properly have a "libcall" store, but we can use `LibCall::all()`
+                // and index into that.
+                let index = LibCall::all_libcalls()
+                    .iter()
+                    .position(|lc| lc == libcall)
+                    .unwrap();
+
+                (AddressFunctionEntry::LibCall, index as u32)
+            }
+            _ => unimplemented!("function_address: {:?}", name),
+        };
+
+        Address::from_parts(size, AddressRegion::Function, entry as u64, index as u64)
+    }
+
+    fn get_function_from_address(&self, address: Address) -> Option<InterpreterFunctionRef<'a>> {
+        let index = address.offset as u32;
+        if address.region != AddressRegion::Function {
+            return None;
+        }
+
+        match AddressFunctionEntry::from(address.entry) {
+            AddressFunctionEntry::UserFunction => self
+                .functions
+                .get_by_index(FuncIndex::from_u32(index))
+                .map(InterpreterFunctionRef::from),
+
+            AddressFunctionEntry::LibCall => LibCall::all_libcalls()
+                .get(index as usize)
+                .copied()
+                .map(InterpreterFunctionRef::from),
+        }
+    }
+
     /// Non-Recursively resolves a global value until its address is found
     fn resolve_global_value(&self, gv: GlobalValue) -> Result<DataValue, MemoryError> {
         // Resolving a Global Value is a "pointer" chasing operation that lends itself to
diff --git a/cranelift/interpreter/src/state.rs b/cranelift/interpreter/src/state.rs
index 49526e4a8..b98f0c552 100644
--- a/cranelift/interpreter/src/state.rs
+++ b/cranelift/interpreter/src/state.rs
@@ -3,7 +3,10 @@
 use crate::address::{Address, AddressSize};
 use crate::interpreter::LibCallHandler;
 use cranelift_codegen::data_value::DataValue;
-use cranelift_codegen::ir::{FuncRef, Function, GlobalValue, StackSlot, Type, Value};
+use cranelift_codegen::ir::{
+    ExternalName, FuncRef, Function, GlobalValue, LibCall, Signature, StackSlot, Type, Value,
+};
+use cranelift_codegen::isa::CallConv;
 use cranelift_entity::PrimaryMap;
 use smallvec::SmallVec;
 use thiserror::Error;
@@ -64,6 +67,16 @@ pub trait State<'a, V> {
     /// stack or to one of the heaps; the number of bytes stored corresponds to the specified [Type].
     fn checked_store(&mut self, address: Address, v: V) -> Result<(), MemoryError>;
 
+    /// Compute the address of a function given its name.
+    fn function_address(
+        &self,
+        size: AddressSize,
+        name: &ExternalName,
+    ) -> Result<Address, MemoryError>;
+
+    /// Retrieve a reference to a [Function] given its address.
+    fn get_function_from_address(&self, address: Address) -> Option<InterpreterFunctionRef<'a>>;
+
     /// Given a global value, compute the final value for that global value, applying all operations
     /// in intermediate global values.
     fn resolve_global_value(&self, gv: GlobalValue) -> Result<V, MemoryError>;
@@ -77,6 +90,33 @@ pub trait State<'a, V> {
     fn set_pinned_reg(&mut self, v: V);
 }
 
+pub enum InterpreterFunctionRef<'a> {
+    Function(&'a Function),
+    LibCall(LibCall),
+}
+
+impl<'a> InterpreterFunctionRef<'a> {
+    pub fn signature(&self) -> Signature {
+        match self {
+            InterpreterFunctionRef::Function(f) => f.stencil.signature.clone(),
+            // CallConv here is sort of irrelevant, since we don't use it for anything
+            InterpreterFunctionRef::LibCall(lc) => lc.signature(CallConv::SystemV),
+        }
+    }
+}
+
+impl<'a> From<&'a Function> for InterpreterFunctionRef<'a> {
+    fn from(f: &'a Function) -> Self {
+        InterpreterFunctionRef::Function(f)
+    }
+}
+
+impl From<LibCall> for InterpreterFunctionRef<'_> {
+    fn from(lc: LibCall) -> Self {
+        InterpreterFunctionRef::LibCall(lc)
+    }
+}
+
 #[derive(Error, Debug)]
 pub enum MemoryError {
     #[error("Invalid DataValue passed as an address: {0}")]
@@ -150,6 +190,18 @@ where
         unimplemented!()
     }
 
+    fn function_address(
+        &self,
+        _size: AddressSize,
+        _name: &ExternalName,
+    ) -> Result<Address, MemoryError> {
+        unimplemented!()
+    }
+
+    fn get_function_from_address(&self, _address: Address) -> Option<InterpreterFunctionRef<'a>> {
+        unimplemented!()
+    }
+
     fn resolve_global_value(&self, _gv: GlobalValue) -> Result<V, MemoryError> {
         unimplemented!()
     }
diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs
index c77a5fa43..e64d90838 100644
--- a/cranelift/interpreter/src/step.rs
+++ b/cranelift/interpreter/src/step.rs
@@ -2,7 +2,7 @@
 //! [InstructionContext]; the interpretation is generic over [Value]s.
 use crate::address::{Address, AddressSize};
 use crate::instruction::InstructionContext;
-use crate::state::{MemoryError, State};
+use crate::state::{InterpreterFunctionRef, MemoryError, State};
 use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
@@ -276,35 +276,12 @@ where
         }
     };
 
-    // Perform a call operation.
-    //
-    // The returned `ControlFlow` variant is determined by the given function
-    // argument, which should make either a `ControlFlow::Call` or a
-    // `ControlFlow::ReturnCall`.
-    let do_call = |make_ctrl_flow: fn(&'a Function, SmallVec<[V; 1]>) -> ControlFlow<'a, V>|
+    // Calls a function reference with the given arguments.
+    let call_func = |func_ref: InterpreterFunctionRef<'a>,
+                     args: SmallVec<[V; 1]>,
+                     make_ctrl_flow: fn(&'a Function, SmallVec<[V; 1]>) -> ControlFlow<'a, V>|
      -> Result<ControlFlow<'a, V>, StepError> {
-        let func_ref = if let InstructionData::Call { func_ref, .. } = inst {
-            func_ref
-        } else {
-            unreachable!()
-        };
-
-        let curr_func = state.get_current_function();
-        let ext_data = curr_func
-            .dfg
-            .ext_funcs
-            .get(func_ref)
-            .ok_or(StepError::UnknownFunction(func_ref))?;
-
-        let signature = if let Some(sig) = curr_func.dfg.signatures.get(ext_data.signature) {
-            sig
-        } else {
-            return Ok(ControlFlow::Trap(CraneliftTrap::User(
-                TrapCode::BadSignature,
-            )));
-        };
-
-        let args = args()?;
+        let signature = func_ref.signature();
 
         // Check the types of the arguments. This is usually done by the verifier, but nothing
         // guarantees that the user has ran that.
@@ -315,17 +292,16 @@ where
             )));
         }
 
-        Ok(match ext_data.name {
-            // These functions should be registered in the regular function store
-            ExternalName::User(_) | ExternalName::TestCase(_) => {
-                let function = state
-                    .get_function(func_ref)
-                    .ok_or(StepError::UnknownFunction(func_ref))?;
-
-                make_ctrl_flow(function, args)
-            }
-            ExternalName::LibCall(libcall) => {
-                debug_assert_ne!(inst.opcode(), Opcode::ReturnCall, "Cannot tail call to libcalls");
+        Ok(match func_ref {
+            InterpreterFunctionRef::Function(func) => make_ctrl_flow(func, args),
+            InterpreterFunctionRef::LibCall(libcall) => {
+                debug_assert!(
+                    !matches!(
+                        inst.opcode(),
+                        Opcode::ReturnCall | Opcode::ReturnCallIndirect,
+                    ),
+                    "Cannot tail call to libcalls"
+                );
                 let libcall_handler = state.get_libcall_handler();
 
                 // We don't transfer control to a libcall, we just execute it and return the results
@@ -342,7 +318,6 @@ where
                     ControlFlow::Trap(CraneliftTrap::User(TrapCode::BadSignature))
                 }
             }
-            ExternalName::KnownSymbol(_) => unimplemented!(),
         })
     };
 
@@ -398,11 +373,83 @@ where
         Opcode::Trapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::User(trap_code())),
         Opcode::ResumableTrapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::Resumable),
         Opcode::Return => ControlFlow::Return(args()?),
-        Opcode::Call => do_call(ControlFlow::Call)?,
-        Opcode::CallIndirect => unimplemented!("CallIndirect"),
-        Opcode::ReturnCall => do_call(ControlFlow::ReturnCall)?,
-        Opcode::ReturnCallIndirect => unimplemented!("ReturnCallIndirect"),
-        Opcode::FuncAddr => unimplemented!("FuncAddr"),
+        Opcode::Call | Opcode::ReturnCall => {
+            let func_ref = if let InstructionData::Call { func_ref, .. } = inst {
+                func_ref
+            } else {
+                unreachable!()
+            };
+
+            let curr_func = state.get_current_function();
+            let ext_data = curr_func
+                .dfg
+                .ext_funcs
+                .get(func_ref)
+                .ok_or(StepError::UnknownFunction(func_ref))?;
+
+            let args = args()?;
+            let func = match ext_data.name {
+                // These functions should be registered in the regular function store
+                ExternalName::User(_) | ExternalName::TestCase(_) => {
+                    let function = state
+                        .get_function(func_ref)
+                        .ok_or(StepError::UnknownFunction(func_ref))?;
+                    InterpreterFunctionRef::Function(function)
+                }
+                ExternalName::LibCall(libcall) => InterpreterFunctionRef::LibCall(libcall),
+                ExternalName::KnownSymbol(_) => unimplemented!(),
+            };
+
+            let make_control_flow = match inst.opcode() {
+                Opcode::Call => ControlFlow::Call,
+                Opcode::ReturnCall => ControlFlow::ReturnCall,
+                _ => unreachable!(),
+            };
+
+            call_func(func, args, make_control_flow)?
+        }
+        Opcode::CallIndirect | Opcode::ReturnCallIndirect => {
+            let args = args()?;
+            let addr_dv = DataValue::U64(arg(0)?.into_int()? as u64);
+            let addr = Address::try_from(addr_dv.clone()).map_err(StepError::MemoryError)?;
+
+            let func = state
+                .get_function_from_address(addr)
+                .ok_or_else(|| StepError::MemoryError(MemoryError::InvalidAddress(addr_dv)))?;
+
+            let call_args: SmallVec<[V; 1]> = SmallVec::from(&args[1..]);
+
+            let make_control_flow = match inst.opcode() {
+                Opcode::CallIndirect => ControlFlow::Call,
+                Opcode::ReturnCallIndirect => ControlFlow::ReturnCall,
+                _ => unreachable!(),
+            };
+
+            call_func(func, call_args, make_control_flow)?
+        }
+        Opcode::FuncAddr => {
+            let func_ref = if let InstructionData::FuncAddr { func_ref, .. } = inst {
+                func_ref
+            } else {
+                unreachable!()
+            };
+
+            let ext_data = state
+                .get_current_function()
+                .dfg
+                .ext_funcs
+                .get(func_ref)
+                .ok_or(StepError::UnknownFunction(func_ref))?;
+
+            let addr_ty = inst_context.controlling_type().unwrap();
+            assign_or_memtrap({
+                AddressSize::try_from(addr_ty).and_then(|addr_size| {
+                    let addr = state.function_address(addr_size, &ext_data.name)?;
+                    let dv = DataValue::try_from(addr)?;
+                    Ok(dv.into())
+                })
+            })
+        }
         Opcode::Load
         | Opcode::Uload8
         | Opcode::Sload8
@@ -1222,7 +1269,24 @@ where
                 Address::try_from(addr).and_then(|addr| state.checked_store(addr, replace));
             assign_or_memtrap(stored.map(|_| prev_val_to_assign))
         }
-        Opcode::AtomicCas => unimplemented!("AtomicCas"),
+        Opcode::AtomicCas => {
+            let addr = arg(0)?.into_int()? as u64;
+            let loaded = Address::try_from(addr).and_then(|addr| state.checked_load(addr, ctrl_ty));
+            let loaded_val = match loaded {
+                Ok(v) => v,
+                Err(e) => return Ok(ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e)))),
+            };
+            let expected_val = arg(1)?;
+            let val_to_assign = if Value::eq(&loaded_val, &expected_val)? {
+                let val_to_store = arg(2)?;
+                Address::try_from(addr)
+                    .and_then(|addr| state.checked_store(addr, val_to_store))
+                    .map(|_| loaded_val)
+            } else {
+                Ok(loaded_val)
+            };
+            assign_or_memtrap(val_to_assign)
+        }
         Opcode::AtomicLoad => {
             let load_ty = inst_context.controlling_type().unwrap();
             let addr = arg(0)?.into_int()? as u64;
diff --git a/crates/wasi-common/src/dir.rs b/crates/wasi-common/src/dir.rs
index 48cc10d2f..4f9e4de67 100644
--- a/crates/wasi-common/src/dir.rs
+++ b/crates/wasi-common/src/dir.rs
@@ -218,21 +218,12 @@ impl DirFdStat {
 
 pub(crate) trait TableDirExt {
     fn get_dir(&self, fd: u32) -> Result<Arc<DirEntry>, Error>;
-    fn is_preopen(&self, fd: u32) -> bool;
 }
 
 impl TableDirExt for crate::table::Table {
     fn get_dir(&self, fd: u32) -> Result<Arc<DirEntry>, Error> {
         self.get(fd)
     }
-    fn is_preopen(&self, fd: u32) -> bool {
-        if self.is::<DirEntry>(fd) {
-            let dir_entry: Arc<DirEntry> = self.get(fd).unwrap();
-            dir_entry.preopen_path.is_some()
-        } else {
-            false
-        }
-    }
 }
 
 #[derive(Debug, Clone)]
diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs
index 2d3683054..8b924c9dc 100644
--- a/crates/wasi-common/src/snapshots/preview_1.rs
+++ b/crates/wasi-common/src/snapshots/preview_1.rs
@@ -145,12 +145,6 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
         if table.is::<FileEntry>(fd) {
             let _ = table.delete::<FileEntry>(fd);
         } else if table.is::<DirEntry>(fd) {
-            // We cannot close preopened directories
-            let dir_entry: Arc<DirEntry> = table.get(fd).unwrap();
-            if dir_entry.preopen_path().is_some() {
-                return Err(Error::not_supported().context("cannot close propened directory"));
-            }
-            drop(dir_entry);
             let _ = table.delete::<DirEntry>(fd);
         } else {
             return Err(Error::badf().context("key does not refer to file or directory"));
@@ -533,9 +527,6 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
         if !table.contains_key(from) {
             return Err(Error::badf());
         }
-        if table.is_preopen(from) || table.is_preopen(to) {
-            return Err(Error::not_supported().context("cannot renumber a preopen"));
-        }
         table.renumber(from, to)
     }
 
diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs
index 15212fb92..e5cc6d496 100644
--- a/crates/wasmtime/src/component/mod.rs
+++ b/crates/wasmtime/src/component/mod.rs
@@ -135,7 +135,7 @@ pub(crate) use self::store::ComponentStoreData;
 ///
 ///     // Here our `greet` function doesn't take any parameters for the component,
 ///     // but in the Wasmtime embedding API the first argument is always a `Store`.
-///     bindings.greet(&mut store)?;
+///     bindings.call_greet(&mut store)?;
 ///     Ok(())
 /// }
 /// ```


Test patch:
diff --git a/cranelift/filetests/filetests/egraph/algebraic.clif b/cranelift/filetests/filetests/egraph/algebraic.clif
index 3e8577cdb..24460845a 100644
--- a/cranelift/filetests/filetests/egraph/algebraic.clif
+++ b/cranelift/filetests/filetests/egraph/algebraic.clif
@@ -29,7 +29,7 @@ block0(v0: i8):
     return v3
     ; check: v4 = iconst.i8 224
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
 }
 
 function %unsigned_shift_right_shift_left_i32(i32) -> i32 {
@@ -51,7 +51,7 @@ block0(v0: i64):
     return v3
     ; check: v4 = iconst.i64 -32
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
 }
 
 function %signed_shift_right_shift_left_i8(i8) -> i8 {
@@ -62,7 +62,7 @@ block0(v0: i8):
     return v3
     ; check: v4 = iconst.i8 224
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
 }
 
 function %signed_shift_right_shift_left_i32(i32) -> i32 {
@@ -73,7 +73,7 @@ block0(v0: i32):
     return v3
     ; check: v4 = iconst.i32 0xffff_ffe0
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
 }
 
 function %signed_shift_right_shift_left_i64(i64) -> i64 {
@@ -84,7 +84,7 @@ block0(v0: i64):
     return v3
     ; check: v4 = iconst.i64 -32
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
 }
 
 function %signed_shift_right_shift_left_i8_mask_rhs(i8) -> i8 {
@@ -95,7 +95,133 @@ block0(v0: i8):
     return v3
     ; check: v4 = iconst.i8 224
     ; check: v5 = band v0, v4
-    ; return v5
+    ; check: return v5
+}
+
+function %sextend_shift_32_64_unsigned(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 32
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: v7 = uextend.i64 v0
+    ; check: return v7
+}
+
+function %sextend_shift_32_64_signed(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 32
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %sextend_undershift_32_64_unsigned(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 31
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: v5 = iconst.i64 0x0001_ffff_ffff
+    ; check: v6 = band v2, v5
+    ; check: return v6
+}
+
+function %sextend_undershift_32_64_signed(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 31
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %sextend_shift_8_64_unsigned(i8) -> i64 {
+block0(v0: i8):
+    v1 = iconst.i8 56
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: v7 = uextend.i64 v0
+    ; check: return v7
+}
+
+function %sextend_shift_8_64_signed(i8) -> i64 {
+block0(v0: i8):
+    v1 = iconst.i8 56
+    v2 = sextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %uextend_shift_32_64_unsigned(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 32
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %uextend_shift_32_64_signed(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 32
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: v5 = sextend.i64 v0
+    ; check: return v5
+}
+
+function %uextend_undershift_32_64_unsigned(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 31
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %uextend_undershift_32_64_signed(i32) -> i64 {
+block0(v0: i32):
+    v1 = iconst.i8 31
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %uextend_shift_8_64_unsigned(i8) -> i64 {
+block0(v0: i8):
+    v1 = iconst.i8 56
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = ushr v3, v1
+    return v4
+    ; check: return v2
+}
+
+function %uextend_shift_8_64_signed(i8) -> i64 {
+block0(v0: i8):
+    v1 = iconst.i8 56
+    v2 = uextend.i64 v0
+    v3 = ishl v2, v1
+    v4 = sshr v3, v1
+    return v4
+    ; check: v5 = sextend.i64 v0
+    ; check: return v5
 }
 
 function %or_and_y_with_not_y_i8(i8, i8) -> i8 {
diff --git a/cranelift/filetests/filetests/isa/riscv64/reftypes.clif b/cranelift/filetests/filetests/isa/riscv64/reftypes.clif
index e1e491aca..8f6dcae54 100644
--- a/cranelift/filetests/filetests/isa/riscv64/reftypes.clif
+++ b/cranelift/filetests/filetests/isa/riscv64/reftypes.clif
@@ -23,15 +23,12 @@ block0(v0: r64):
 
 ; VCode:
 ; block0:
-;   is_null a0,a0
+;   seqz a0,a0
 ;   ret
 ; 
 ; Disassembled:
 ; block0: ; offset 0x0
-;   beq zero, a0, 0xc
-;   mv a0, zero
-;   j 8
-;   addi a0, zero, 1
+;   seqz a0, a0
 ;   ret
 
 function %f2(r64) -> i8 {
@@ -42,15 +39,14 @@ block0(v0: r64):
 
 ; VCode:
 ; block0:
-;   is_invalid a0,a0
+;   addi t2,a0,1
+;   seqz a0,t2
 ;   ret
 ; 
 ; Disassembled:
 ; block0: ; offset 0x0
-;   beq zero, a0, 0xc
-;   mv a0, zero
-;   j 8
-;   addi a0, zero, 1
+;   addi t2, a0, 1
+;   seqz a0, t2
 ;   ret
 
 function %f3() -> r64 {
diff --git a/cranelift/filetests/filetests/runtests/atomic-cas-little.clif b/cranelift/filetests/filetests/runtests/atomic-cas-little.clif
index c910befb2..3bc0f59fa 100644
--- a/cranelift/filetests/filetests/runtests/atomic-cas-little.clif
+++ b/cranelift/filetests/filetests/runtests/atomic-cas-little.clif
@@ -1,3 +1,4 @@
+test interpret
 test run
 target s390x
 
diff --git a/cranelift/filetests/filetests/runtests/bitcast-ref64.clif b/cranelift/filetests/filetests/runtests/bitcast-ref64.clif
index 546152b5c..2206f3917 100644
--- a/cranelift/filetests/filetests/runtests/bitcast-ref64.clif
+++ b/cranelift/filetests/filetests/runtests/bitcast-ref64.clif
@@ -2,6 +2,7 @@ test run
 target aarch64
 target x86_64
 target s390x
+target riscv64gc
 ; the interpreter does not support bitcasting to/from references
 
 function %bitcast_ir64(i64) -> i8 {
diff --git a/cranelift/filetests/filetests/runtests/call_indirect.clif b/cranelift/filetests/filetests/runtests/call_indirect.clif
index 3705001c9..6a71e492e 100644
--- a/cranelift/filetests/filetests/runtests/call_indirect.clif
+++ b/cranelift/filetests/filetests/runtests/call_indirect.clif
@@ -1,9 +1,11 @@
+test interpret
 test run
 target x86_64
 target aarch64
 target aarch64 sign_return_address
 target aarch64 has_pauth sign_return_address
 target s390x
+target riscv64gc
 
 
 function %callee_indirect(i64) -> i64 {
diff --git a/cranelift/filetests/filetests/runtests/issue5714.clif b/cranelift/filetests/filetests/runtests/issue5714.clif
new file mode 100644
index 000000000..35cbab7d2
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/issue5714.clif
@@ -0,0 +1,25 @@
+test interpret
+test run
+target aarch64
+target s390x
+target riscv64
+
+function %a(i32 sext, f64, f32, i64 uext) -> f32, i8, i8 system_v {
+block0(v0: i32, v1: f64, v2: f32, v3: i64):
+    v12 -> v1
+    v14 -> v2
+    v16 -> v2
+    v4 = iconst.i16 0x4242
+    v5 = iconst.i64 0x0042_4242_4242_4242
+    jump block1
+
+block1:
+    v13 = fcvt_to_sint_sat.i8 v12
+    v15 -> v13
+    brif v13, block2, block1
+
+block2:
+    return v14, v15, v15
+}
+
+; run: %a(1111441986, -0x1.ffa8b425b4242p128, 0x0.00859ep-126, 0) == [0x0.00859ep-126, -128, -128]
\ No newline at end of file
diff --git a/cranelift/filetests/filetests/runtests/ref64-invalid-null.clif b/cranelift/filetests/filetests/runtests/ref64-invalid-null.clif
index ff321aa27..8486f5630 100644
--- a/cranelift/filetests/filetests/runtests/ref64-invalid-null.clif
+++ b/cranelift/filetests/filetests/runtests/ref64-invalid-null.clif
@@ -3,6 +3,7 @@ test run
 target aarch64
 target x86_64
 target s390x
+target riscv64gc
 
 function %is_null_true_r64() -> i8 {
 block0:
diff --git a/cranelift/filetests/filetests/runtests/return-call-indirect.clif b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
new file mode 100644
index 000000000..4277aaa28
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/return-call-indirect.clif
@@ -0,0 +1,76 @@
+test interpret
+;; test run
+;; target x86_64
+;; target aarch64
+;; target aarch64 sign_return_address
+;; target aarch64 has_pauth sign_return_address
+;; target s390x
+
+;;;; Test passing `i64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i64(i64) -> i64 tail {
+block0(v0: i64):
+    v1 = iadd_imm.i64 v0, 10
+    return v1
+}
+
+function %call_i64(i64) -> i64 tail {
+    fn0 = %callee_i64(i64) -> i64 tail
+    ; sig0 = (i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+; run: %call_i64(10) == 20
+
+;;;; Test colocated tail calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %colocated_i64(i64) -> i64 tail {
+    fn0 = colocated %callee_i64(i64) -> i64 tail
+    ; sig0 = (i64) -> i64 tail
+
+block0(v0: i64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+; run: %colocated_i64(10) == 20
+
+;;;; Test passing `f64`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_f64(f64) -> f64 tail {
+block0(v0: f64):
+    v1 = f64const 0x10.0
+    v2 = fadd.f64 v0, v1
+    return v2
+}
+
+function %call_f64(f64) -> f64 tail {
+    fn0 = %callee_f64(f64) -> f64 tail
+    ; sig0 = (f64) -> f64 tail
+
+block0(v0: f64):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+; run: %call_f64(0x10.0) == 0x20.0
+
+;;;; Test passing `i8`s ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+function %callee_i8(i8) -> i8 tail {
+block0(v0: i8):
+    v1 = iconst.i8 0
+    v2 = icmp eq v0, v1
+    return v2
+}
+
+function %call_i8(i8) -> i8 tail {
+    fn0 = %callee_i8(i8) -> i8 tail
+    ; sig0 = (i8) -> i8 tail
+
+block0(v0: i8):
+    v1 = func_addr.i64 fn0
+    return_call_indirect sig0, v1(v0)
+}
+; run: %call_i8(1) == 0
+; run: %call_i8(0) == 1
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 9f955f44a..2d8f3a344 100644
--- a/crates/test-programs/wasi-tests/src/bin/close_preopen.rs
+++ b/crates/test-programs/wasi-tests/src/bin/close_preopen.rs
@@ -7,17 +7,7 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
     assert!(dir_fd > pre_fd, "dir_fd number");
 
     // Try to close a preopened directory handle.
-    assert_errno!(
-        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"),
-        wasi::ERRNO_NOTSUP
-    );
+    wasi::fd_close(pre_fd).expect("closing a preopened file descriptor");
 
     // Ensure that dir_fd is still open.
     let dir_fdstat = wasi::fd_fdstat_get(dir_fd).expect("failed fd_fdstat_get");
@@ -27,19 +17,10 @@ unsafe fn test_close_preopen(dir_fd: wasi::Fd) {
         "expected the scratch directory to be a directory",
     );
 
-    // Try to renumber a preopened directory handle.
+    // Ensure that pre_fd is closed.
     assert_errno!(
-        wasi::fd_renumber(pre_fd, dir_fd)
-            .expect_err("renumbering over a preopened file descriptor"),
-        wasi::ERRNO_NOTSUP
-    );
-
-    // Ensure that dir_fd is still open.
-    let dir_fdstat = wasi::fd_fdstat_get(dir_fd).expect("failed fd_fdstat_get");
-    assert_eq!(
-        dir_fdstat.fs_filetype,
-        wasi::FILETYPE_DIRECTORY,
-        "expected the scratch directory to be a directory",
+        wasi::fd_fdstat_get(pre_fd).expect_err("failed fd_fdstat_get"),
+        wasi::ERRNO_BADF
     );
 }
 
diff --git a/crates/test-programs/wasi-tests/src/bin/overwrite_preopen.rs b/crates/test-programs/wasi-tests/src/bin/overwrite_preopen.rs
new file mode 100644
index 000000000..7b509c503
--- /dev/null
+++ b/crates/test-programs/wasi-tests/src/bin/overwrite_preopen.rs
@@ -0,0 +1,50 @@
+use std::{env, process};
+use wasi_tests::{assert_errno, open_scratch_directory};
+
+unsafe fn test_overwrite_preopen(dir_fd: wasi::Fd) {
+    let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd;
+
+    assert!(dir_fd > pre_fd, "dir_fd number");
+
+    let old_dir_filestat = wasi::fd_filestat_get(dir_fd).expect("failed fd_filestat_get");
+
+    // Try to renumber over a preopened directory handle.
+    wasi::fd_renumber(dir_fd, pre_fd)
+            .expect("renumbering over a preopened file descriptor");
+
+    // Ensure that pre_fd is still open.
+    let new_dir_filestat = wasi::fd_filestat_get(pre_fd).expect("failed fd_filestat_get");
+
+    // Ensure that we renumbered.
+    assert_eq!(old_dir_filestat.dev, new_dir_filestat.dev);
+    assert_eq!(old_dir_filestat.ino, new_dir_filestat.ino);
+
+    // Ensure that dir_fd is closed.
+    assert_errno!(
+        wasi::fd_fdstat_get(dir_fd).expect_err("failed fd_fdstat_get"),
+        wasi::ERRNO_BADF
+    );
+}
+
+fn main() {
+    let mut args = env::args();
+    let prog = args.next().unwrap();
+    let arg = if let Some(arg) = args.next() {
+        arg
+    } else {
+        eprintln!("usage: {} <scratch directory>", prog);
+        process::exit(1);
+    };
+
+    // Open scratch directory
+    let dir_fd = match open_scratch_directory(&arg) {
+        Ok(dir_fd) => dir_fd,
+        Err(err) => {
+            eprintln!("{}", err);
+            process::exit(1)
+        }
+    };
+
+    // Run the tests.
+    unsafe { test_overwrite_preopen(dir_fd) }
+}