Interpreter: Implement floating point conversions (#4884)
* Interpreter: Implement floating point conversions
Implemented the following opcodes for the interpreter:
- `FcvtToUint`
- `FcvtToSint`
- `FcvtToUintSat`
- `FcvtToSintSat`
- `FcvtFromUint`
- `FcvtFromSint`
- `FcvtLowFromSint`
- `FvpromoteLow`
- `Fvdemote`
Copyright (c) 2022 Arm Limited
* Fix `I128` bounds checks for `FcvtTo{U,S}int{_,Sat}`
Copyright (c) 2022 Arm Limited
* Fix broken test
Copyright (c) 2022 Arm Limited
This commit is contained in:
55
cranelift/filetests/filetests/runtests/conversion.clif
Normal file
55
cranelift/filetests/filetests/runtests/conversion.clif
Normal file
@@ -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
|
||||
52
cranelift/filetests/filetests/runtests/i128-conversion.clif
Normal file
52
cranelift/filetests/filetests/runtests/i128-conversion.clif
Normal file
@@ -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
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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))?,
|
||||
|
||||
@@ -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()?),
|
||||
|
||||
Reference in New Issue
Block a user