use super::isa::{ExtendOp, SimdMode, SimdShape};
#[must_use]
pub(super) fn simd(mode: SimdMode, shape: SimdShape, a: u32, b: u32) -> (u32, Option<u8>) {
match shape {
SimdShape::Add8 | SimdShape::Sub8 => simd8(mode, shape == SimdShape::Sub8, a, b),
_ => simd16(mode, shape, a, b),
}
}
fn simd16(mode: SimdMode, shape: SimdShape, a: u32, b: u32) -> (u32, Option<u8>) {
let unsigned = mode.is_unsigned();
let a_lo = lane16(a, 0, unsigned);
let a_hi = lane16(a, 1, unsigned);
let (b_lo, b_hi) = match shape {
SimdShape::Asx | SimdShape::Sax => (lane16(b, 1, unsigned), lane16(b, 0, unsigned)),
_ => (lane16(b, 0, unsigned), lane16(b, 1, unsigned)),
};
let (sub_lo, sub_hi) = match shape {
SimdShape::Add16 => (false, false),
SimdShape::Sub16 => (true, true),
SimdShape::Asx => (true, false),
_ => (false, true),
};
let (lo, ge_lo) = lane_op(mode, sub_lo, a_lo, b_lo, 16);
let (hi, ge_hi) = lane_op(mode, sub_hi, a_hi, b_hi, 16);
let result = ((lo as u32) & 0xffff) | (((hi as u32) & 0xffff) << 16);
let ge = if mode.sets_ge() {
Some((u8::from(ge_lo) * 0b0011) | (u8::from(ge_hi) * 0b1100))
} else {
None
};
(result, ge)
}
fn simd8(mode: SimdMode, sub: bool, a: u32, b: u32) -> (u32, Option<u8>) {
let unsigned = mode.is_unsigned();
let mut result = 0u32;
let mut ge = 0u8;
for k in 0..4 {
let x = lane8(a, k, unsigned);
let y = lane8(b, k, unsigned);
let (v, g) = lane_op(mode, sub, x, y, 8);
result |= ((v as u32) & 0xff) << (8 * k);
if g {
ge |= 1 << k;
}
}
(result, if mode.sets_ge() { Some(ge) } else { None })
}
fn lane_op(mode: SimdMode, sub: bool, a: i32, b: i32, width: u32) -> (i32, bool) {
let raw = if sub { a - b } else { a + b };
let ge = if mode.is_unsigned() {
if sub { raw >= 0 } else { raw >= (1 << width) }
} else {
raw >= 0
};
let value = match mode {
SimdMode::Signed | SimdMode::Unsigned => raw,
SimdMode::SignedSat => {
let max = (1i32 << (width - 1)) - 1;
let min = -(1i32 << (width - 1));
raw.clamp(min, max)
}
SimdMode::UnsignedSat => raw.clamp(0, (1i32 << width) - 1),
SimdMode::SignedHalve => raw >> 1,
SimdMode::UnsignedHalve => ((raw as u32) >> 1) as i32,
};
(value, ge)
}
const fn lane16(value: u32, k: u32, unsigned: bool) -> i32 {
let half = (value >> (16 * k)) as u16;
if unsigned {
half as i32
} else {
(half as i16) as i32
}
}
const fn lane8(value: u32, k: u32, unsigned: bool) -> i32 {
let byte = (value >> (8 * k)) as u8;
if unsigned {
byte as i32
} else {
(byte as i8) as i32
}
}
#[must_use]
pub(super) const fn extend(op: ExtendOp, rotated: u32) -> u32 {
match op {
ExtendOp::Sxtb => ((rotated as u8) as i8) as i32 as u32,
ExtendOp::Sxth => ((rotated as u16) as i16) as i32 as u32,
ExtendOp::Uxtb => rotated & 0xff,
ExtendOp::Uxth => rotated & 0xffff,
ExtendOp::Sxtb16 => {
let lo = ((rotated as u8) as i8) as i32 as u32 & 0xffff;
let hi = (((rotated >> 16) as u8) as i8) as i32 as u32 & 0xffff;
lo | (hi << 16)
}
ExtendOp::Uxtb16 => (rotated & 0xff) | ((rotated >> 16) & 0xff) << 16,
}
}
#[must_use]
pub(super) const fn extend_accumulate(op: ExtendOp, acc: u32, value: u32) -> u32 {
match op {
ExtendOp::Sxtb16 | ExtendOp::Uxtb16 => {
let lo = (acc as u16).wrapping_add(value as u16) as u32;
let hi = ((acc >> 16) as u16).wrapping_add((value >> 16) as u16) as u32;
lo | (hi << 16)
}
_ => acc.wrapping_add(value),
}
}