Skip to content

Commit

Permalink
Interpreter: Implement floating point conversions (#4884)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dheaton-arm committed Sep 20, 2022
1 parent 63c9e5d commit cae7c19
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 13 deletions.
55 changes: 55 additions & 0 deletions cranelift/filetests/filetests/runtests/conversion.clif
Original file line number Diff line number Diff line change
@@ -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 changes: 52 additions & 0 deletions cranelift/filetests/filetests/runtests/i128-conversion.clif
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions cranelift/filetests/filetests/runtests/simd-conversion.clif
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
test interpret
test run
target aarch64
target s390x
Expand Down Expand Up @@ -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]

140 changes: 131 additions & 9 deletions cranelift/interpreter/src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))?,
Expand Down
23 changes: 19 additions & 4 deletions cranelift/interpreter/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()?),
Expand Down

0 comments on commit cae7c19

Please sign in to comment.