Implement Extractlane, UaddSat, and UsubSat for Cranelift interpreter (#3188)

* Implement `Extractlane`, `UaddSat`, and `UsubSat` for Cranelift interpreter

Implemented the `Extractlane`, `UaddSat`, and `UsubSat` opcodes for the interpreter,
and added helper functions for working with SIMD vectors (`extractlanes`, `vectorizelanes`,
and `binary_arith`).

Copyright (c) 2021, Arm Limited

* Re-use tests + constrict Vector assert

- Re-use interpreter tests as runtests where supported.
- Constrict Vector assertion.
- Code style adjustments following feedback.

Copyright (c) 2021, Arm Limited

* Runtest `i32x4` vectors on AArch64; add `i64x2` tests

Copyright (c) 2021, Arm Limited

* Add `simd-` prefix to test filenames

Copyright (c) 2021, Arm Limited

* Return aliased `SmallVec` from `extractlanes`

Using a `SmallVec<[i128; 4]>` allows larger-width 128-bit vectors
(`i32x4`, `i64x2`, ...) to not cause heap allocations.

Copyright (c) 2021, Arm Limited

* Accept slice to `vectorizelanes` rather than `Vec`

Copyright (c) 2021, Arm Limited
This commit is contained in:
Damian Heaton
2021-08-25 17:03:19 +01:00
committed by GitHub
parent 7d05ebe7ff
commit 02ef6a02b8
7 changed files with 234 additions and 5 deletions

View File

@@ -496,10 +496,22 @@ where
binary(Value::div, inc, two)?
}
Opcode::Iadd => binary(Value::add, arg(0)?, arg(1)?)?,
Opcode::UaddSat => unimplemented!("UaddSat"),
Opcode::UaddSat => assign(binary_arith(
arg(0)?,
arg(1)?,
ctrl_ty,
Value::add_sat,
true,
)?),
Opcode::SaddSat => unimplemented!("SaddSat"),
Opcode::Isub => binary(Value::sub, arg(0)?, arg(1)?)?,
Opcode::UsubSat => unimplemented!("UsubSat"),
Opcode::UsubSat => assign(binary_arith(
arg(0)?,
arg(1)?,
ctrl_ty,
Value::sub_sat,
true,
)?),
Opcode::SsubSat => unimplemented!("SsubSat"),
Opcode::Ineg => binary(Value::sub, Value::int(0, ctrl_ty)?, arg(0)?)?,
Opcode::Iabs => unimplemented!("Iabs"),
@@ -661,7 +673,11 @@ where
Opcode::Swizzle => unimplemented!("Swizzle"),
Opcode::Splat => unimplemented!("Splat"),
Opcode::Insertlane => unimplemented!("Insertlane"),
Opcode::Extractlane => unimplemented!("Extractlane"),
Opcode::Extractlane => {
let value =
extractlanes(&arg(0)?, ctrl_ty.lane_type())?[Value::into_int(imm())? as usize];
assign(Value::int(value, ctrl_ty.lane_type())?)
}
Opcode::VhighBits => unimplemented!("VhighBits"),
Opcode::Vsplit => unimplemented!("Vsplit"),
Opcode::Vconcat => unimplemented!("Vconcat"),
@@ -869,3 +885,80 @@ where
}
})
}
type SimdVec = SmallVec<[i128; 4]>;
/// Converts a SIMD vector value into a Rust vector of i128 for processing.
fn extractlanes<V>(x: &V, lane_type: types::Type) -> ValueResult<SimdVec>
where
V: Value,
{
let iterations = match lane_type {
types::I8 => 1,
types::I16 => 2,
types::I32 => 4,
types::I64 => 8,
_ => unimplemented!("Only 128-bit vectors are currently supported."),
};
let x = x.into_array()?;
let mut lanes = SimdVec::new();
for (i, _) in x.iter().enumerate() {
let mut lane: i128 = 0;
if i % iterations != 0 {
continue;
}
for j in 0..iterations {
lane += (x[i + j] as i128) << (8 * j);
}
lanes.push(lane);
}
return Ok(lanes);
}
/// Convert a Rust array of i128s back into a `Value::vector`.
fn vectorizelanes<V>(x: &[i128], vector_type: types::Type) -> ValueResult<V>
where
V: Value,
{
let iterations = match vector_type.lane_type() {
types::I8 => 1,
types::I16 => 2,
types::I32 => 4,
types::I64 => 8,
_ => unimplemented!("Only 128-bit vectors are currently supported."),
};
let mut result: [u8; 16] = [0; 16];
for (i, val) in x.iter().enumerate() {
let val = *val;
for j in 0..iterations {
result[(i * iterations) + j] = (val >> (8 * j)) as u8;
}
}
Value::vector(result, vector_type)
}
/// Performs the supplied binary arithmetic `op` on two SIMD vectors.
fn binary_arith<V, F>(x: V, y: V, vector_type: types::Type, op: F, unsigned: bool) -> ValueResult<V>
where
V: Value,
F: Fn(V, V) -> ValueResult<V>,
{
let arg0 = extractlanes(&x, vector_type.lane_type())?;
let arg1 = extractlanes(&y, vector_type.lane_type())?;
let mut result = Vec::new();
for (lhs, rhs) in arg0.into_iter().zip(arg1) {
// The initial Value::int needs to be on a separate line so the
// compiler can determine concrete types.
let mut lhs: V = Value::int(lhs, vector_type.lane_type())?;
let mut rhs: V = Value::int(rhs, vector_type.lane_type())?;
if unsigned {
lhs = lhs.convert(ValueConversionKind::ToUnsigned)?;
rhs = rhs.convert(ValueConversionKind::ToUnsigned)?;
}
let sum = op(lhs, rhs)?;
let sum = sum.into_int()?;
result.push(sum);
}
vectorizelanes(&result, vector_type)
}

View File

@@ -22,6 +22,7 @@ pub trait Value: Clone + From<DataValue> {
fn bool(b: bool, ty: Type) -> ValueResult<Self>;
fn into_bool(self) -> ValueResult<bool>;
fn vector(v: [u8; 16], ty: Type) -> ValueResult<Self>;
fn into_array(&self) -> ValueResult<[u8; 16]>;
fn convert(self, kind: ValueConversionKind) -> ValueResult<Self>;
fn concat(self, other: Self) -> ValueResult<Self>;
@@ -47,6 +48,10 @@ pub trait Value: Clone + From<DataValue> {
fn div(self, other: Self) -> ValueResult<Self>;
fn rem(self, other: Self) -> ValueResult<Self>;
// Saturating arithmetic.
fn add_sat(self, other: Self) -> ValueResult<Self>;
fn sub_sat(self, other: Self) -> ValueResult<Self>;
// Bitwise.
fn shl(self, other: Self) -> ValueResult<Self>;
fn ushr(self, other: Self) -> ValueResult<Self>;
@@ -80,6 +85,7 @@ pub enum ValueTypeClass {
Integer,
Boolean,
Float,
Vector,
}
impl Display for ValueTypeClass {
@@ -88,6 +94,7 @@ impl Display for ValueTypeClass {
ValueTypeClass::Integer => write!(f, "integer"),
ValueTypeClass::Boolean => write!(f, "boolean"),
ValueTypeClass::Float => write!(f, "float"),
ValueTypeClass::Vector => write!(f, "vector"),
}
}
}
@@ -225,8 +232,16 @@ impl Value for DataValue {
}
}
fn vector(_v: [u8; 16], _ty: Type) -> ValueResult<Self> {
unimplemented!()
fn vector(v: [u8; 16], ty: Type) -> ValueResult<Self> {
assert!(ty.is_vector() && ty.bytes() == 16);
Ok(DataValue::V128(v))
}
fn into_array(&self) -> ValueResult<[u8; 16]> {
match *self {
DataValue::V128(v) => Ok(v),
_ => Err(ValueError::InvalidType(ValueTypeClass::Vector, self.ty())),
}
}
fn convert(self, kind: ValueConversionKind) -> ValueResult<Self> {
@@ -380,6 +395,14 @@ impl Value for DataValue {
binary_match!(%(&self, &other); [I8, I16, I32, I64])
}
fn add_sat(self, other: Self) -> ValueResult<Self> {
binary_match!(saturating_add(self, &other); [I8, I16, I32, I64, I128, U8, U16, U32, U64, U128])
}
fn sub_sat(self, other: Self) -> ValueResult<Self> {
binary_match!(saturating_sub(self, &other); [I8, I16, I32, I64, I128, U8, U16, U32, U64, U128])
}
fn shl(self, other: Self) -> ValueResult<Self> {
binary_match!(<<(&self, &other); [I8, I16, I32, I64])
}