diff --git a/cranelift/filetests/filetests/runtests/conversion.clif b/cranelift/filetests/filetests/runtests/conversion.clif
new file mode 100644
index 000000000000..011abcfc743c
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/conversion.clif
@@ -0,0 +1,55 @@
+test interpret
+test run
+target aarch64
+target s390x
+target x86_64
+
+function %fcvt_to_sint(f32) -> i32 {
+block0(v0: f32):
+    v1 = fcvt_to_sint.i32 v0
+    return v1
+}
+; run: %fcvt_to_sint(0x0.0) == 0
+; run: %fcvt_to_sint(0x1.0) == 1
+; run: %fcvt_to_sint(0x1.d6f346p26) == 123456792
+; run: %fcvt_to_sint(0x8.1) == 8
+
+function %fcvt_to_uint(f32) -> i32 {
+block0(v0:f32):
+    v1 = fcvt_to_uint.i32 v0
+    return v1
+}
+; run: %fcvt_to_uint(0x0.0) == 0
+; run: %fcvt_to_uint(0x1.0) == 1
+; run: %fcvt_to_uint(0x4.2) == 4
+; run: %fcvt_to_uint(0x4.6) == 4
+; run: %fcvt_to_uint(0x1.d6f346p26) == 123456792
+; run: %fcvt_to_uint(0xB2D05E00.0) == 3000000000
+
+function %fcvt_to_sint_sat(f32) -> i32 {
+block0(v0: f32):
+    v1 = fcvt_to_sint_sat.i32 v0
+    return v1
+}
+; run: %fcvt_to_sint_sat(0x0.0) == 0
+; run: %fcvt_to_sint_sat(0x1.0) == 1
+; run: %fcvt_to_sint_sat(0x1.d6f346p26) == 123456792
+; run: %fcvt_to_sint_sat(0x8.1) == 8
+; run: %fcvt_to_sint_sat(-0x1.0) == -1
+; run: %fcvt_to_sint_sat(0x1.fffffep127) == 2147483647
+; run: %fcvt_to_sint_sat(-0x1.fffffep127) == -2147483648
+
+function %fcvt_to_uint_sat(f32) -> i32 {
+block0(v0:f32):
+    v1 = fcvt_to_uint_sat.i32 v0
+    return v1
+}
+; run: %fcvt_to_uint_sat(0x0.0) == 0
+; run: %fcvt_to_uint_sat(0x1.0) == 1
+; run: %fcvt_to_uint_sat(0x4.2) == 4
+; run: %fcvt_to_uint_sat(0x4.6) == 4
+; run: %fcvt_to_uint_sat(0x1.d6f346p26) == 123456792
+; run: %fcvt_to_uint_sat(0xB2D05E00.0) == 3000000000
+; run: %fcvt_to_uint_sat(-0x1.0) == 0
+; run: %fcvt_to_uint_sat(0x1.fffffep127) == 4294967295
+; run: %fcvt_to_uint_sat(-0x1.fffffep127) == 0
diff --git a/cranelift/filetests/filetests/runtests/conversions.clif b/cranelift/filetests/filetests/runtests/conversions-load-store.clif
similarity index 100%
rename from cranelift/filetests/filetests/runtests/conversions.clif
rename to cranelift/filetests/filetests/runtests/conversions-load-store.clif
diff --git a/cranelift/filetests/filetests/runtests/i128-conversion.clif b/cranelift/filetests/filetests/runtests/i128-conversion.clif
new file mode 100644
index 000000000000..16ba8c7520b8
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/i128-conversion.clif
@@ -0,0 +1,52 @@
+test interpret
+; `fcvt_to_{u,s}int.i128` not currently supported by any backend.
+
+function %fcvt_to_uint_i128(f32) -> i128 {
+block0(v0: f32):
+    v1 = fcvt_to_uint.i128 v0
+    return v1
+}
+; run: %fcvt_to_uint_i128(0x0.0) == 0
+; run: %fcvt_to_uint_i128(0x1.0) == 1
+; run: %fcvt_to_uint_i128(0x1.0p31) == 2147483648
+; run: %fcvt_to_uint_i128(0x1.fffffp31) == 4294965248
+; run: %fcvt_to_uint_i128(0x1.0p63) == 9223372036854775808
+; run: %fcvt_to_uint_i128(0x1.fffffep127) == 170141183460469231731687303715884105727
+
+function %fcvt_to_sint_i128(f32) -> i128 {
+block0(v0: f32):
+    v1 = fcvt_to_sint.i128 v0
+    return v1
+}
+; run: %fcvt_to_sint_i128(0x0.0) == 0
+; run: %fcvt_to_sint_i128(0x1.0) == 1
+; run: %fcvt_to_sint_i128(0x1.0p31) == 2147483648
+; run: %fcvt_to_sint_i128(0x1.fffffp31) == 4294965248
+; run: %fcvt_to_sint_i128(-0x1.fffffp31) == -4294965248
+; run: %fcvt_to_sint_i128(0x1.0p63) == 9223372036854775808
+; run: %fcvt_to_sint_i128(-0x1.0p63) == -9223372036854775808
+; run: %fcvt_to_sint_i128(0x1.fffffep127) == 170141183460469231731687303715884105727
+
+function %fcvt_to_uint_sat_i128(f32) -> i128 {
+block0(v0: f32):
+    v1 = fcvt_to_uint_sat.i128 v0
+    return v1
+}
+; run: %fcvt_to_uint_sat_i128(0x0.0) == 0
+; run: %fcvt_to_uint_sat_i128(0x1.0) == 1
+; run: %fcvt_to_uint_sat_i128(0x1.0p31) == 2147483648
+; run: %fcvt_to_uint_sat_i128(0x1.fffffp31) == 4294965248
+; run: %fcvt_to_uint_sat_i128(-0x1.fffffp31) == 0
+; run: %fcvt_to_uint_sat_i128(0x1.fffffep127) == 170141183460469231731687303715884105727
+
+function %fcvt_to_sint_sat_i128(f32) -> i128 {
+block0(v0: f32):
+    v1 = fcvt_to_sint_sat.i128 v0
+    return v1
+}
+; run: %fcvt_to_sint_sat_i128(0x0.0) == 0
+; run: %fcvt_to_sint_sat_i128(0x1.0) == 1
+; run: %fcvt_to_sint_sat_i128(0x1.0p31) == 2147483648
+; run: %fcvt_to_sint_sat_i128(0x1.fffffp31) == 4294965248
+; run: %fcvt_to_sint_sat_i128(-0x1.fffffp31) == -4294965248
+; run: %fcvt_to_sint_sat_i128(0x1.fffffep127) == 170141183460469231731687303715884105727
diff --git a/cranelift/filetests/filetests/runtests/simd-conversion.clif b/cranelift/filetests/filetests/runtests/simd-conversion.clif
index 62cff744818e..6866b679f2f7 100644
--- a/cranelift/filetests/filetests/runtests/simd-conversion.clif
+++ b/cranelift/filetests/filetests/runtests/simd-conversion.clif
@@ -1,3 +1,4 @@
+test interpret
 test run
 target aarch64
 target s390x
@@ -47,3 +48,29 @@ block0(v0: i32x4):
 }
 ; run: %fcvt_low_from_sint([0 1 -1 65535]) == [0x0.0 0x1.0]
 ; run: %fcvt_low_from_sint([-1 123456789 0 1]) == [-0x1.0 0x1.d6f3454p26]
+
+function %fvdemote(f64x2) -> f32x4 {
+block0(v0: f64x2):
+    v1 = fvdemote v0
+    return v1
+}
+
+; run: %fvdemote([0x0.0 0x0.0]) == [0x0.0 0x0.0 0x0.0 0x0.0]
+; run: %fvdemote([0x0.1 0x0.2]) == [0x0.1 0x0.2 0x0.0 0x0.0]
+; run: %fvdemote([0x2.1 0x1.2]) == [0x2.1 0x1.2 0x0.0 0x0.0]
+; run: %fvdemote([0x2.1 0x1.2]) == [0x2.1 0x1.2 0x0.0 0x0.0]
+; run: %fvdemote([0x2.1 0x1.2]) == [0x2.1 0x1.2 0x0.0 0x0.0]
+
+
+function %fvpromote_low(f32x4) -> f64x2 {
+block0(v0: f32x4):
+    v1 = fvpromote_low v0
+    return v1
+}
+
+; run: %fvpromote_low([0x0.0 0x0.0 0x0.0 0x0.0]) == [0x0.0 0x0.0]
+; run: %fvpromote_low([0x0.1 0x0.2 0x0.0 0x0.0]) == [0x0.1 0x0.2]
+; run: %fvpromote_low([0x2.1 0x1.2 0x0.0 0x0.0]) == [0x2.1 0x1.2]
+; run: %fvpromote_low([0x0.0 0x0.0 0x2.1 0x1.2]) == [0x0.0 0x0.0]
+; run: %fvpromote_low([0x0.0 0x0.0 0x2.1 0x1.2]) == [0x0.0 0x0.0]
+
diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs
index 280c2c91f78d..9b92328dde62 100644
--- a/cranelift/interpreter/src/step.rs
+++ b/cranelift/interpreter/src/step.rs
@@ -1119,15 +1119,137 @@ where
             };
             assign(vectorizelanes(&new_vec, new_type)?)
         }
-        Opcode::FcvtToUint => unimplemented!("FcvtToUint"),
-        Opcode::FcvtToUintSat => unimplemented!("FcvtToUintSat"),
-        Opcode::FcvtToSint => unimplemented!("FcvtToSint"),
-        Opcode::FcvtToSintSat => unimplemented!("FcvtToSintSat"),
-        Opcode::FcvtFromUint => unimplemented!("FcvtFromUint"),
-        Opcode::FcvtFromSint => unimplemented!("FcvtFromSint"),
-        Opcode::FcvtLowFromSint => unimplemented!("FcvtLowFromSint"),
-        Opcode::FvpromoteLow => unimplemented!("FvpromoteLow"),
-        Opcode::Fvdemote => unimplemented!("Fvdemote"),
+        Opcode::FcvtToUint | Opcode::FcvtToSint => {
+            // NaN check
+            if arg(0)?.is_nan()? {
+                return Ok(ControlFlow::Trap(CraneliftTrap::User(
+                    TrapCode::BadConversionToInteger,
+                )));
+            }
+            let x = arg(0)?.into_float()? as i128;
+            let is_signed = inst.opcode() == Opcode::FcvtToSint;
+            let (min, max) = ctrl_ty.bounds(is_signed);
+            let overflow = if is_signed {
+                x < (min as i128) || x > (max as i128)
+            } else {
+                x < 0 || (x as u128) > (max as u128)
+            };
+            // bounds check
+            if overflow {
+                return Ok(ControlFlow::Trap(CraneliftTrap::User(
+                    TrapCode::IntegerOverflow,
+                )));
+            }
+            // perform the conversion.
+            assign(Value::int(x, ctrl_ty)?)
+        }
+        Opcode::FcvtToUintSat | Opcode::FcvtToSintSat => {
+            let in_ty = inst_context.type_of(inst_context.args()[0]).unwrap();
+            let cvt = |x: V| -> ValueResult<V> {
+                // NaN check
+                if x.is_nan()? {
+                    V::int(0, ctrl_ty.lane_type())
+                } else {
+                    let is_signed = inst.opcode() == Opcode::FcvtToSintSat;
+                    let (min, max) = ctrl_ty.bounds(is_signed);
+                    let x = x.into_float()? as i128;
+                    let x = if is_signed {
+                        let x = i128::max(x, min as i128);
+                        let x = i128::min(x, max as i128);
+                        x
+                    } else {
+                        let x = if x < 0 { 0 } else { x };
+                        let x = u128::min(x as u128, max as u128);
+                        x as i128
+                    };
+                    V::int(x, ctrl_ty.lane_type())
+                }
+            };
+
+            let x = extractlanes(&arg(0)?, in_ty)?;
+
+            assign(vectorizelanes(
+                &x.into_iter()
+                    .map(cvt)
+                    .collect::<ValueResult<SimdVec<V>>>()?,
+                ctrl_ty,
+            )?)
+        }
+        Opcode::FcvtFromUint | Opcode::FcvtFromSint => {
+            let x = extractlanes(
+                &arg(0)?,
+                inst_context.type_of(inst_context.args()[0]).unwrap(),
+            )?;
+            let bits = |x: V| -> ValueResult<u64> {
+                let x = if inst.opcode() == Opcode::FcvtFromUint {
+                    x.convert(ValueConversionKind::ToUnsigned)?
+                } else {
+                    x
+                };
+                Ok(match ctrl_ty.lane_type() {
+                    types::F32 => (x.into_int()? as f32).to_bits() as u64,
+                    types::F64 => (x.into_int()? as f64).to_bits(),
+                    _ => unimplemented!("unexpected conversion to {:?}", ctrl_ty.lane_type()),
+                })
+            };
+            assign(vectorizelanes(
+                &x.into_iter()
+                    .map(|x| V::float(bits(x)?, ctrl_ty.lane_type()))
+                    .collect::<ValueResult<SimdVec<V>>>()?,
+                ctrl_ty,
+            )?)
+        }
+        Opcode::FcvtLowFromSint => {
+            let in_ty = inst_context.type_of(inst_context.args()[0]).unwrap();
+            let x = extractlanes(&arg(0)?, in_ty)?;
+
+            assign(vectorizelanes(
+                &(x[..(ctrl_ty.lane_count() as usize)]
+                    .into_iter()
+                    .map(|x| {
+                        V::float(
+                            match ctrl_ty.lane_type() {
+                                types::F32 => (x.to_owned().into_int()? as f32).to_bits() as u64,
+                                types::F64 => (x.to_owned().into_int()? as f64).to_bits(),
+                                _ => unimplemented!("unexpected promotion to {:?}", ctrl_ty),
+                            },
+                            ctrl_ty.lane_type(),
+                        )
+                    })
+                    .collect::<ValueResult<SimdVec<V>>>()?),
+                ctrl_ty,
+            )?)
+        }
+        Opcode::FvpromoteLow => {
+            let in_ty = inst_context.type_of(inst_context.args()[0]).unwrap();
+            assert_eq!(in_ty, types::F32X4);
+            let out_ty = types::F64X2;
+            let x = extractlanes(&arg(0)?, in_ty)?;
+            assign(vectorizelanes(
+                &x[..(out_ty.lane_count() as usize)]
+                    .into_iter()
+                    .map(|x| {
+                        V::convert(x.to_owned(), ValueConversionKind::Exact(out_ty.lane_type()))
+                    })
+                    .collect::<ValueResult<SimdVec<V>>>()?,
+                out_ty,
+            )?)
+        }
+        Opcode::Fvdemote => {
+            let in_ty = inst_context.type_of(inst_context.args()[0]).unwrap();
+            assert_eq!(in_ty, types::F64X2);
+            let out_ty = types::F32X4;
+            let x = extractlanes(&arg(0)?, in_ty)?;
+            let x = &mut x
+                .into_iter()
+                .map(|x| V::convert(x, ValueConversionKind::RoundNearestEven(out_ty.lane_type())))
+                .collect::<ValueResult<SimdVec<V>>>()?;
+            // zero the high bits.
+            for _ in 0..(out_ty.lane_count() as usize - x.len()) {
+                x.push(V::float(0, out_ty.lane_type())?);
+            }
+            assign(vectorizelanes(x, out_ty)?)
+        }
         Opcode::Isplit => assign_multiple(&[
             Value::convert(arg(0)?, ValueConversionKind::Truncate(types::I64))?,
             Value::convert(arg(0)?, ValueConversionKind::ExtractUpper(types::I64))?,
diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs
index 204195944ca3..fefd308107e5 100644
--- a/cranelift/interpreter/src/value.rs
+++ b/cranelift/interpreter/src/value.rs
@@ -246,7 +246,11 @@ impl Value for DataValue {
     }
 
     fn into_float(self) -> ValueResult<f64> {
-        unimplemented!()
+        match self {
+            DataValue::F32(n) => Ok(n.as_f32() as f64),
+            DataValue::F64(n) => Ok(n.as_f64()),
+            _ => Err(ValueError::InvalidType(ValueTypeClass::Float, self.ty())),
+        }
     }
 
     fn is_float(&self) -> bool {
@@ -307,8 +311,11 @@ impl Value for DataValue {
                 (val, ty) if val.ty().is_int() && ty.is_int() => {
                     DataValue::from_integer(val.into_int()?, ty)?
                 }
+                (DataValue::I32(n), types::F32) => DataValue::F32(f32::from_bits(n as u32).into()),
+                (DataValue::I64(n), types::F64) => DataValue::F64(f64::from_bits(n as u64).into()),
                 (DataValue::F32(n), types::I32) => DataValue::I32(n.bits() as i32),
                 (DataValue::F64(n), types::I64) => DataValue::I64(n.bits() as i64),
+                (DataValue::F32(n), types::F64) => DataValue::F64((n.as_f32() as f64).into()),
                 (DataValue::B(b), t) if t.is_bool() => DataValue::B(b),
                 (DataValue::B(b), t) if t.is_int() => {
                     // Bools are represented in memory as all 1's
@@ -412,9 +419,17 @@ impl Value for DataValue {
                 DataValue::U128(n) => DataValue::I128(n as i128),
                 _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
             },
-            ValueConversionKind::RoundNearestEven(ty) => match (self.ty(), ty) {
-                (types::F64, types::F32) => unimplemented!(),
-                _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
+            ValueConversionKind::RoundNearestEven(ty) => match (self, ty) {
+                (DataValue::F64(n), types::F32) => {
+                    let mut x = n.as_f64() as f32;
+                    // Rust rounds away from zero, so if we've rounded up we
+                    // should replace this with a proper rounding tied to even.
+                    if (x as f64) != n.as_f64() {
+                        x = n.round_ties_even().as_f64() as f32;
+                    }
+                    DataValue::F32(x.into())
+                }
+                (s, _) => unimplemented!("conversion: {} -> {:?}", s.ty(), kind),
             },
             ValueConversionKind::ToBoolean => match self.ty() {
                 ty if ty.is_bool() => DataValue::B(self.into_bool()?),