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
|
test run
|
||||||
target aarch64
|
target aarch64
|
||||||
target s390x
|
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([0 1 -1 65535]) == [0x0.0 0x1.0]
|
||||||
; run: %fcvt_low_from_sint([-1 123456789 0 1]) == [-0x1.0 0x1.d6f3454p26]
|
; 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)?)
|
assign(vectorizelanes(&new_vec, new_type)?)
|
||||||
}
|
}
|
||||||
Opcode::FcvtToUint => unimplemented!("FcvtToUint"),
|
Opcode::FcvtToUint | Opcode::FcvtToSint => {
|
||||||
Opcode::FcvtToUintSat => unimplemented!("FcvtToUintSat"),
|
// NaN check
|
||||||
Opcode::FcvtToSint => unimplemented!("FcvtToSint"),
|
if arg(0)?.is_nan()? {
|
||||||
Opcode::FcvtToSintSat => unimplemented!("FcvtToSintSat"),
|
return Ok(ControlFlow::Trap(CraneliftTrap::User(
|
||||||
Opcode::FcvtFromUint => unimplemented!("FcvtFromUint"),
|
TrapCode::BadConversionToInteger,
|
||||||
Opcode::FcvtFromSint => unimplemented!("FcvtFromSint"),
|
)));
|
||||||
Opcode::FcvtLowFromSint => unimplemented!("FcvtLowFromSint"),
|
}
|
||||||
Opcode::FvpromoteLow => unimplemented!("FvpromoteLow"),
|
let x = arg(0)?.into_float()? as i128;
|
||||||
Opcode::Fvdemote => unimplemented!("Fvdemote"),
|
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(&[
|
Opcode::Isplit => assign_multiple(&[
|
||||||
Value::convert(arg(0)?, ValueConversionKind::Truncate(types::I64))?,
|
Value::convert(arg(0)?, ValueConversionKind::Truncate(types::I64))?,
|
||||||
Value::convert(arg(0)?, ValueConversionKind::ExtractUpper(types::I64))?,
|
Value::convert(arg(0)?, ValueConversionKind::ExtractUpper(types::I64))?,
|
||||||
|
|||||||
@@ -246,7 +246,11 @@ impl Value for DataValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn into_float(self) -> ValueResult<f64> {
|
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 {
|
fn is_float(&self) -> bool {
|
||||||
@@ -307,8 +311,11 @@ impl Value for DataValue {
|
|||||||
(val, ty) if val.ty().is_int() && ty.is_int() => {
|
(val, ty) if val.ty().is_int() && ty.is_int() => {
|
||||||
DataValue::from_integer(val.into_int()?, ty)?
|
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::F32(n), types::I32) => DataValue::I32(n.bits() as i32),
|
||||||
(DataValue::F64(n), types::I64) => DataValue::I64(n.bits() as i64),
|
(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_bool() => DataValue::B(b),
|
||||||
(DataValue::B(b), t) if t.is_int() => {
|
(DataValue::B(b), t) if t.is_int() => {
|
||||||
// Bools are represented in memory as all 1's
|
// 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),
|
DataValue::U128(n) => DataValue::I128(n as i128),
|
||||||
_ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
|
_ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
|
||||||
},
|
},
|
||||||
ValueConversionKind::RoundNearestEven(ty) => match (self.ty(), ty) {
|
ValueConversionKind::RoundNearestEven(ty) => match (self, ty) {
|
||||||
(types::F64, types::F32) => unimplemented!(),
|
(DataValue::F64(n), types::F32) => {
|
||||||
_ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
|
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() {
|
ValueConversionKind::ToBoolean => match self.ty() {
|
||||||
ty if ty.is_bool() => DataValue::B(self.into_bool()?),
|
ty if ty.is_bool() => DataValue::B(self.into_bool()?),
|
||||||
|
|||||||
Reference in New Issue
Block a user