use std::mem::offset_of;
use cranelift_codegen::ir::{InstBuilder, Value, condcodes::IntCC, types};
use super::super::family::Family;
use super::super::registers::{ccr, is_set};
use super::super::state::State;
use super::alu::{add_with_carry, signed_sat_q, unsigned_sat_q};
use super::{Emitter, Fault, Translated, Undefined, bits};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Half {
Bottom,
Top,
}
impl Half {
fn of(bit: u32) -> Self {
if bit == 0 { Self::Bottom } else { Self::Top }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Combine {
Add,
Subtract,
}
impl Combine {
fn apply(self, e: &mut Emitter<'_, '_>, x: Value, y: Value) -> Value {
match self {
Self::Add => e.b.ins().iadd(x, y),
Self::Subtract => e.b.ins().isub(x, y),
}
}
}
#[derive(Clone, Copy)]
struct Operands {
rn: Value,
rm: Value,
ra: Option<Value>,
}
fn usable(registers: &[u32]) -> bool {
registers.iter().all(|&r| r != 13 && r != 15)
}
pub(super) fn multiply_accumulate(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let (op1, op2) = (bits(hw1, 6, 4), bits(hw2, 5, 4));
let (n, a, d, m) = (
bits(hw1, 3, 0),
bits(hw2, 15, 12),
bits(hw2, 11, 8),
bits(hw2, 3, 0),
);
let accumulate = a != 15;
let exchange_or_round = op2 & 1 != 0;
let operation = match (op1, op2) {
(0b000, 0b00) => Accumulating::Multiply,
(0b000, 0b01) if accumulate => Accumulating::MultiplySubtract,
(0b001, _) => Accumulating::Halfwords(Half::of(op2 >> 1), Half::of(op2 & 1)),
(0b010, 0b00 | 0b01) => Accumulating::Dual(Combine::Add, exchange_or_round),
(0b011, 0b00 | 0b01) => Accumulating::WordByHalfword(Half::of(op2)),
(0b100, 0b00 | 0b01) => Accumulating::Dual(Combine::Subtract, exchange_or_round),
(0b101, 0b00 | 0b01) => Accumulating::MostSignificant(Combine::Add, exchange_or_round),
(0b110, 0b00 | 0b01) if accumulate => {
Accumulating::MostSignificant(Combine::Subtract, exchange_or_round)
}
(0b111, 0b00) => Accumulating::AbsoluteDifferences,
_ => return Err(Undefined),
};
if bits(hw2, 7, 6) != 0 || !usable(&[d, n, m]) || a == 13 {
return Err(Undefined);
}
let (rn, rm) = (e.reg(n), e.reg(m));
let ra = accumulate.then(|| e.reg(a));
let operands = Operands { rn, rm, ra };
let result = match operation {
Accumulating::Multiply => {
let product = e.b.ins().imul(rn, rm);
ra.map_or(product, |ra| e.b.ins().iadd(product, ra))
}
Accumulating::MultiplySubtract => {
let product = e.b.ins().imul(rn, rm);
e.b.ins().isub(ra.expect("MLS accumulates"), product)
}
Accumulating::Halfwords(of_n, of_m) => halfwords(e, operands, of_n, of_m),
Accumulating::Dual(combine, swap) => dual(e, operands, combine, swap),
Accumulating::WordByHalfword(of_m) => word_by_halfword(e, operands, of_m),
Accumulating::MostSignificant(combine, round) => {
most_significant(e, operands, combine, round)
}
Accumulating::AbsoluteDifferences => absolute_differences(e, operands),
};
e.set_reg(d, result);
Ok(())
}
#[derive(Clone, Copy)]
enum Accumulating {
Multiply,
MultiplySubtract,
Halfwords(Half, Half),
Dual(Combine, bool),
WordByHalfword(Half),
MostSignificant(Combine, bool),
AbsoluteDifferences,
}
fn halfwords(e: &mut Emitter<'_, '_>, operands: Operands, x: Half, y: Half) -> Value {
let (operand1, operand2) = (half(e, operands.rn, x), half(e, operands.rm, y));
let product = e.b.ins().imul(operand1, operand2);
let Some(ra) = operands.ra else {
return product;
};
let carry_in = e.constant(0);
let (result, _, overflow) = add_with_carry(e, product, ra, carry_in);
set_q_when(e, overflow);
result
}
fn dual(e: &mut Emitter<'_, '_>, operands: Operands, combine: Combine, swap: bool) -> Value {
let mut result = dual_sum(e, operands.rn, operands.rm, combine, swap);
if let Some(ra) = operands.ra {
let ra = e.b.ins().sextend(types::I64, ra);
result = e.b.ins().iadd(result, ra);
}
let overflow = overflows(e, result);
set_q_when(e, overflow);
e.b.ins().ireduce(types::I32, result)
}
fn word_by_halfword(e: &mut Emitter<'_, '_>, operands: Operands, y: Half) -> Value {
let operand2 = half(e, operands.rm, y);
let operand1 = e.b.ins().sextend(types::I64, operands.rn);
let operand2 = e.b.ins().sextend(types::I64, operand2);
let mut product = e.b.ins().imul(operand1, operand2);
if let Some(ra) = operands.ra {
let ra = e.b.ins().sextend(types::I64, ra);
let ra = e.b.ins().ishl_imm_u(ra, 16);
product = e.b.ins().iadd(product, ra);
}
let shifted = e.b.ins().sshr_imm_u(product, 16);
if operands.ra.is_some() {
let overflow = overflows(e, shifted);
set_q_when(e, overflow);
}
e.b.ins().ireduce(types::I32, shifted)
}
fn most_significant(
e: &mut Emitter<'_, '_>,
operands: Operands,
combine: Combine,
round: bool,
) -> Value {
let product = long_product(e, operands.rn, operands.rm, true);
let mut result = match operands.ra {
Some(ra) => {
let ra = e.b.ins().uextend(types::I64, ra);
let ra = e.b.ins().ishl_imm_u(ra, 32);
combine.apply(e, ra, product)
}
None => product,
};
if round {
result = e.b.ins().iadd_imm_u(result, 0x8000_0000_i64);
}
high_word(e, result)
}
fn absolute_differences(e: &mut Emitter<'_, '_>, operands: Operands) -> Value {
let mut sum = operands.ra.unwrap_or_else(|| e.constant(0));
for byte in 0..4 {
let x = lane(e, operands.rn, byte, 8, false);
let y = lane(e, operands.rm, byte, 8, false);
let difference = e.b.ins().isub(x, y);
let difference = e.b.ins().iabs(difference);
sum = e.b.ins().iadd(sum, difference);
}
sum
}
pub(super) fn long_multiply_divide(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let (op1, op2) = (bits(hw1, 6, 4), bits(hw2, 7, 4));
let swap = op2 & 1 != 0;
let operation = match (op1, op2) {
(0b001, 0b1111) => return divide(e, hw1, hw2, true),
(0b011, 0b1111) => return divide(e, hw1, hw2, false),
(0b000, 0b0000) => Long::Multiply(true),
(0b010, 0b0000) => Long::Multiply(false),
(0b100, 0b0000) => Long::Accumulate(true),
(0b110, 0b0000) => Long::Accumulate(false),
(0b100, 0b1000..=0b1011) => Long::Halfwords(Half::of(op2 >> 1 & 1), Half::of(op2 & 1)),
(0b100, 0b1100 | 0b1101) => Long::Dual(Combine::Add, swap),
(0b101, 0b1100 | 0b1101) => Long::Dual(Combine::Subtract, swap),
(0b110, 0b0110) => Long::AccumulateAccumulate,
_ => return Err(Undefined),
};
let (n, lo, hi, m) = (
bits(hw1, 3, 0),
bits(hw2, 15, 12),
bits(hw2, 11, 8),
bits(hw2, 3, 0),
);
if !usable(&[lo, hi, n, m]) || lo == hi {
return Err(Undefined);
}
let (rn, rm) = (e.reg(n), e.reg(m));
let (rd_lo, rd_hi) = (e.reg(lo), e.reg(hi));
let rd_lo = e.b.ins().uextend(types::I64, rd_lo);
let rd_hi = e.b.ins().uextend(types::I64, rd_hi);
let result = match operation {
Long::Multiply(signed) => long_product(e, rn, rm, signed),
Long::Accumulate(signed) => {
let product = long_product(e, rn, rm, signed);
let accumulator = pair(e, rd_lo, rd_hi);
e.b.ins().iadd(product, accumulator)
}
Long::Halfwords(x, y) => {
let (operand1, operand2) = (half(e, rn, x), half(e, rm, y));
let product = e.b.ins().imul(operand1, operand2);
let product = e.b.ins().sextend(types::I64, product);
let accumulator = pair(e, rd_lo, rd_hi);
e.b.ins().iadd(product, accumulator)
}
Long::Dual(combine, swap) => {
let sum = dual_sum(e, rn, rm, combine, swap);
let accumulator = pair(e, rd_lo, rd_hi);
e.b.ins().iadd(sum, accumulator)
}
Long::AccumulateAccumulate => {
let product = long_product(e, rn, rm, false);
let sum = e.b.ins().iadd(product, rd_lo);
e.b.ins().iadd(sum, rd_hi)
}
};
let low = e.b.ins().ireduce(types::I32, result);
let high = high_word(e, result);
e.set_reg(hi, high);
e.set_reg(lo, low);
Ok(())
}
#[derive(Clone, Copy)]
enum Long {
Multiply(bool),
Accumulate(bool),
Halfwords(Half, Half),
Dual(Combine, bool),
AccumulateAccumulate,
}
fn long_product(e: &mut Emitter<'_, '_>, rn: Value, rm: Value, signed: bool) -> Value {
let (x, y) = if signed {
(
e.b.ins().sextend(types::I64, rn),
e.b.ins().sextend(types::I64, rm),
)
} else {
(
e.b.ins().uextend(types::I64, rn),
e.b.ins().uextend(types::I64, rm),
)
};
e.b.ins().imul(x, y)
}
fn pair(e: &mut Emitter<'_, '_>, rd_lo: Value, rd_hi: Value) -> Value {
let rd_hi = e.b.ins().ishl_imm_u(rd_hi, 32);
e.b.ins().bor(rd_hi, rd_lo)
}
fn divide(e: &mut Emitter<'_, '_>, hw1: u32, hw2: u32, signed: bool) -> Translated {
let (n, d, m) = (bits(hw1, 3, 0), bits(hw2, 11, 8), bits(hw2, 3, 0));
if bits(hw2, 15, 12) != 0b1111 || !usable(&[d, n, m]) {
return Err(Undefined);
}
let (rn, rm) = (e.reg(n), e.reg(m));
let zero = e.b.ins().icmp_imm_s(IntCC::Equal, rm, 0);
let check = e.b.create_block();
let divide = e.b.create_block();
e.b.ins().brif(zero, check, &[], divide, &[]);
e.b.seal_block(check);
e.b.switch_to_block(check);
let traps = e.call_plain(zero_divide_traps as *const () as usize, &[]);
e.raise_if(traps, Fault::DivideByZero);
e.b.ins().jump(divide, &[]);
e.b.seal_block(divide);
e.b.switch_to_block(divide);
let one = e.constant(1);
let divisor = e.b.ins().select(zero, one, rm);
let quotient = if signed {
let dividend = e.b.ins().sextend(types::I64, rn);
let divisor = e.b.ins().sextend(types::I64, divisor);
let quotient = e.b.ins().sdiv(dividend, divisor);
e.b.ins().ireduce(types::I32, quotient)
} else {
e.b.ins().udiv(rn, divisor)
};
let nothing = e.constant(0);
let result = e.b.ins().select(zero, nothing, quotient);
e.set_reg(d, result);
Ok(())
}
extern "C" fn zero_divide_traps(core: &mut Family) -> u32 {
u32::from(is_set(ccr::DIV_0_TRP, core.system.ccr))
}
pub(super) fn parallel_signed(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
parallel(e, u32::from(hw1), u32::from(hw2), true)
}
pub(super) fn parallel_unsigned(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
parallel(e, u32::from(hw1), u32::from(hw2), false)
}
fn parallel(e: &mut Emitter<'_, '_>, hw1: u32, hw2: u32, signed: bool) -> Translated {
use Combine::{Add, Subtract};
let (op1, op2) = (bits(hw1, 6, 4), bits(hw2, 5, 4));
let (n, d, m) = (bits(hw1, 3, 0), bits(hw2, 11, 8), bits(hw2, 3, 0));
let (width, lanes): (u32, &[(u32, u32, Combine)]) = match op1 {
0b000 => (8, &[(0, 0, Add), (1, 1, Add), (2, 2, Add), (3, 3, Add)]),
0b100 => (
8,
&[
(0, 0, Subtract),
(1, 1, Subtract),
(2, 2, Subtract),
(3, 3, Subtract),
],
),
0b001 => (16, &[(0, 0, Add), (1, 1, Add)]),
0b101 => (16, &[(0, 0, Subtract), (1, 1, Subtract)]),
0b010 => (16, &[(0, 1, Subtract), (1, 0, Add)]),
0b110 => (16, &[(0, 1, Add), (1, 0, Subtract)]),
_ => return Err(Undefined),
};
if op2 == 0b11 || bits(hw2, 15, 12) != 0b1111 || !usable(&[d, n, m]) {
return Err(Undefined);
}
let (rn, rm) = (e.reg(n), e.reg(m));
let mut result = e.constant(0);
let mut ge = e.constant(0);
for &(at, from, combine) in lanes {
let x = lane(e, rn, at, width, signed);
let y = lane(e, rm, from, width, signed);
let exact = combine.apply(e, x, y);
let kept = match op2 {
0b00 => {
let (condition, bound) = if signed || combine == Subtract {
(IntCC::SignedGreaterThanOrEqual, 0)
} else {
(IntCC::UnsignedGreaterThanOrEqual, 1 << width)
};
let set = e.b.ins().icmp_imm_s(condition, exact, bound);
let lane_bits = if width == 16 {
0b11 << (2 * at)
} else {
1 << at
};
let (lane_bits, clear) = (e.constant(lane_bits), e.constant(0));
let lane_bits = e.b.ins().select(set, lane_bits, clear);
ge = e.b.ins().bor(ge, lane_bits);
exact
}
0b01 => {
let exact = e.b.ins().sextend(types::I64, exact);
if signed {
signed_sat_q(e, exact, width).0
} else {
unsigned_sat_q(e, exact, width).0
}
}
_ => e.b.ins().sshr_imm_u(exact, 1),
};
let kept = e.b.ins().band_imm_u(kept, (1_i64 << width) - 1);
let kept = e.b.ins().ishl_imm_u(kept, i64::from(at * width));
result = e.b.ins().bor(result, kept);
}
e.set_reg(d, result);
if op2 == 0b00 {
e.store_field(offset_of!(State, ge), ge);
}
Ok(())
}
pub(super) fn miscellaneous(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let (op1, op2) = (bits(hw1, 5, 4), bits(hw2, 5, 4));
let (n, d, m) = (bits(hw1, 3, 0), bits(hw2, 11, 8), bits(hw2, 3, 0));
if (op1 >= 0b10 && op2 != 0b00) || bits(hw2, 15, 12) != 0b1111 || !usable(&[d, n, m]) {
return Err(Undefined);
}
if op1 & 1 == 1 && n != m {
return Err(Undefined);
}
let result = match op1 {
0b00 => {
let combine = if op2 & 0b10 == 0 {
Combine::Add
} else {
Combine::Subtract
};
saturating(e, n, m, combine, op2 & 1 != 0)
}
0b10 => select_bytes(e, n, m),
_ => {
let rm = e.reg(m);
reverse_count(e, rm, op1, op2)
}
};
e.set_reg(d, result);
Ok(())
}
fn saturating(e: &mut Emitter<'_, '_>, n: u32, m: u32, combine: Combine, double: bool) -> Value {
let (rn, rm) = (e.reg(n), e.reg(m));
let mut operand = e.b.ins().sextend(types::I64, rn);
let mut saturated = e.constant(0);
if double {
let twice = e.b.ins().ishl_imm_u(operand, 1);
let (doubled, doubling_saturated) = signed_sat_q(e, twice, 32);
operand = e.b.ins().sextend(types::I64, doubled);
saturated = doubling_saturated;
}
let rm = e.b.ins().sextend(types::I64, rm);
let exact = combine.apply(e, rm, operand);
let (result, result_saturated) = signed_sat_q(e, exact, 32);
let saturated = e.b.ins().bor(saturated, result_saturated);
set_q_when(e, saturated);
result
}
fn select_bytes(e: &mut Emitter<'_, '_>, n: u32, m: u32) -> Value {
let (rn, rm) = (e.reg(n), e.reg(m));
let ge = e.load_field(offset_of!(State, ge));
let mut mask = e.constant(0);
for byte in 0..4 {
let bit = e.b.ins().ushr_imm_u(ge, byte);
let bit = e.b.ins().band_imm_u(bit, 1);
let lanes = e.b.ins().imul_imm_u(bit, 0xFF_i64 << (8 * byte));
mask = e.b.ins().bor(mask, lanes);
}
let from_n = e.b.ins().band(rn, mask);
let from_m = e.b.ins().band_not(rm, mask);
e.b.ins().bor(from_n, from_m)
}
fn reverse_count(e: &mut Emitter<'_, '_>, rm: Value, op1: u32, op2: u32) -> Value {
match (op1, op2) {
(0b01, 0b00) => e.b.ins().bswap(rm),
(0b01, 0b01) => {
let swapped = e.b.ins().bswap(rm);
e.b.ins().rotr_imm_u(swapped, 16)
}
(0b01, 0b10) => e.b.ins().bitrev(rm),
(0b01, _) => {
let top = e.b.ins().ishl_imm_u(rm, 24);
let top = e.b.ins().sshr_imm_u(top, 16);
let bottom = e.b.ins().ushr_imm_u(rm, 8);
let bottom = e.b.ins().band_imm_u(bottom, 0xFF);
e.b.ins().bor(top, bottom)
}
_ => e.b.ins().clz(rm),
}
}
fn dual_sum(e: &mut Emitter<'_, '_>, rn: Value, rm: Value, combine: Combine, swap: bool) -> Value {
let operand2 = if swap {
e.b.ins().rotr_imm_u(rm, 16)
} else {
rm
};
let [product1, product2] = [Half::Bottom, Half::Top].map(|which| {
let (x, y) = (half(e, rn, which), half(e, operand2, which));
let product = e.b.ins().imul(x, y);
e.b.ins().sextend(types::I64, product)
});
combine.apply(e, product1, product2)
}
fn half(e: &mut Emitter<'_, '_>, value: Value, which: Half) -> Value {
lane(e, value, u32::from(which == Half::Top), 16, true)
}
fn lane(e: &mut Emitter<'_, '_>, value: Value, index: u32, width: u32, signed: bool) -> Value {
let low = index * width;
if signed {
let top = e.b.ins().ishl_imm_u(value, i64::from(32 - width - low));
e.b.ins().sshr_imm_u(top, i64::from(32 - width))
} else {
let shifted = e.b.ins().ushr_imm_u(value, i64::from(low));
e.b.ins().band_imm_u(shifted, (1_i64 << width) - 1)
}
}
fn high_word(e: &mut Emitter<'_, '_>, value: Value) -> Value {
let shifted = e.b.ins().ushr_imm_u(value, 32);
e.b.ins().ireduce(types::I32, shifted)
}
fn overflows(e: &mut Emitter<'_, '_>, value: Value) -> Value {
let low = e.b.ins().ireduce(types::I32, value);
let back = e.b.ins().sextend(types::I64, low);
let differs = e.b.ins().icmp(IntCC::NotEqual, back, value);
e.b.ins().uextend(types::I32, differs)
}
fn set_q_when(e: &mut Emitter<'_, '_>, happened: Value) {
let q = e.load_field(offset_of!(State, q));
let q = e.b.ins().bor(q, happened);
e.store_field(offset_of!(State, q), q);
}
#[cfg(test)]
mod tests {
use cranelift_codegen::ir;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use super::super::super::family::helpers;
use super::super::super::registers::mask;
use super::super::status;
use super::super::testing::Bench;
use super::*;
fn decoded(instruction: u32) -> Translated {
let mut function = ir::Function::new();
function
.signature
.params
.push(ir::AbiParam::new(types::I64));
let mut context = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut function, &mut context);
let entry = builder.create_block();
builder.append_block_params_for_function_params(entry);
builder.switch_to_block(entry);
let mut e = Emitter::new(&mut builder, types::I64);
let (hw1, hw2) = halfwords_of(instruction);
super::super::decode32(&mut e, hw1, hw2)
}
fn halfwords_of(instruction: u32) -> (u16, u16) {
let [hw1_high, hw1_low, hw2_high, hw2_low] = instruction.to_be_bytes();
(
u16::from_be_bytes([hw1_high, hw1_low]),
u16::from_be_bytes([hw2_high, hw2_low]),
)
}
fn run(instruction: u32, r: [u32; 4], q: u32, ge: u32) -> Bench {
let (hw1, hw2) = halfwords_of(instruction);
let mut bench = Bench::new(&[hw1, hw2]);
let state = &mut bench.core.state;
state.r[..4].copy_from_slice(&r);
(state.q, state.ge) = (q, ge);
(state.n, state.z, state.c, state.v) = (1, 0, 1, 1);
let answer = run_block(&mut bench, &[instruction]);
let state = &bench.core.state;
assert_eq!(
(answer, state.r[15]),
(1 << 8 | status::NEXT, 4),
"{instruction:#010x} retired"
);
assert_eq!((state.n, state.z, state.c, state.v), (1, 0, 1, 1));
bench
}
fn run_block(bench: &mut Bench, instructions: &[u32]) -> u32 {
helpers::run_built(&mut bench.core, |builder, core| {
let pointer = builder.func.dfg.value_type(core);
let mut e = Emitter::new(builder, pointer);
for (index, &instruction) in (0..).zip(instructions) {
e.begin(index, index * 4, 4, 0);
let (hw1, hw2) = halfwords_of(instruction);
assert_eq!(
super::super::decode32(&mut e, hw1, hw2),
Ok(()),
"{instruction:#010x} translates"
);
if e.ended() {
e.finish();
return;
}
}
e.end_block();
e.finish();
})
}
fn registers(instruction: u32, r: [u32; 4]) -> [u32; 4] {
let bench = run(instruction, r, 0, 0);
assert_eq!(
(bench.core.state.q, bench.core.state.ge),
(0, 0),
"{instruction:#010x} keeps Q and GE"
);
bench.core.state.r[..4].try_into().unwrap()
}
fn saturated(instruction: u32, r: [u32; 4]) -> (u32, u32) {
let bench = run(instruction, r, 0, 0);
(bench.core.state.r[0], bench.core.state.q)
}
#[test]
fn mul_mla_and_mls_keep_the_low_word() {
const MUL: u32 = 0xFB01_F002; const MLA: u32 = 0xFB01_3002; const MLS: u32 = 0xFB01_3012; assert_eq!(registers(MUL, [9, 7, 6, 0])[0], 42);
assert_eq!(registers(MUL, [9, 0xFFFF_FFFF, 5, 0])[0], 0xFFFF_FFFB);
assert_eq!(registers(MUL, [9, 0x1_0000, 0x1_0000, 0])[0], 0);
assert_eq!(registers(MLA, [9, 3, 4, 5]), [17, 3, 4, 5]);
assert_eq!(registers(MLA, [9, 0xFFFF_FFFF, 2, 1])[0], 0xFFFF_FFFF);
assert_eq!(registers(MLS, [9, 3, 4, 100])[0], 88);
assert_eq!(registers(MLS, [9, 1, 1, 0])[0], 0xFFFF_FFFF);
}
#[test]
fn smul_by_halfwords_multiplies_the_halves_it_names() {
let r = [9, 0x0003_FFFE, 0xFFFF_0005, 0];
assert_eq!(registers(0xFB11_F002, r)[0], 0xFFFF_FFF6, "SMULBB -2 * 5");
assert_eq!(registers(0xFB11_F012, r)[0], 2, "SMULBT -2 * -1");
assert_eq!(registers(0xFB11_F022, r)[0], 15, "SMULTB 3 * 5");
assert_eq!(registers(0xFB11_F032, r)[0], 0xFFFF_FFFD, "SMULTT 3 * -1");
}
#[test]
fn smla_by_halfwords_sets_q_on_an_overflowing_accumulate() {
const SMLABB: u32 = 0xFB11_3002; const SMLATT: u32 = 0xFB11_3032;
assert_eq!(registers(SMLABB, [9, 0x0003_FFFE, 0xFFFF_0005, 100])[0], 90);
assert_eq!(
registers(SMLATT, [9, 0x0003_FFFE, 0xFFFF_0005, 0])[0],
0xFFFF_FFFD
);
assert_eq!(
saturated(SMLABB, [9, 0x8000, 0x8000, 0x4000_0000]),
(0x8000_0000, 1)
);
let bench = run(SMLABB, [9, 2, 3, 4], 1, 0);
assert_eq!((bench.core.state.r[0], bench.core.state.q), (10, 1));
}
#[test]
fn smuad_and_smlad_add_both_products() {
let r = [9, 0x0002_0003, 0x0004_0005, 100];
assert_eq!(registers(0xFB21_F002, r)[0], 23, "SMUAD 3*5 + 2*4");
assert_eq!(registers(0xFB21_F012, r)[0], 22, "SMUADX 3*4 + 2*5");
assert_eq!(registers(0xFB21_3002, r)[0], 123, "SMLAD");
assert_eq!(registers(0xFB21_3012, r)[0], 122, "SMLADX");
assert_eq!(
saturated(0xFB21_F002, [9, 0x8000_8000, 0x8000_8000, 0]),
(0x8000_0000, 1),
"SMUAD of four -32768s is 2^31"
);
assert_eq!(
saturated(0xFB21_3002, [9, 0x0001_0001, 0x0001_0001, 0x7FFF_FFFF]),
(0x8000_0001, 1)
);
}
#[test]
fn smusd_and_smlsd_subtract_the_top_product() {
let r = [9, 0x0002_0003, 0x0004_0005, 100];
assert_eq!(registers(0xFB41_F002, r)[0], 7, "SMUSD 3*5 - 2*4");
assert_eq!(registers(0xFB41_F012, r)[0], 2, "SMUSDX 3*4 - 2*5");
assert_eq!(registers(0xFB41_3002, r)[0], 107, "SMLSD");
assert_eq!(registers(0xFB41_3012, r)[0], 102, "SMLSDX");
assert_eq!(
registers(0xFB41_F002, [9, 0x0003_0002, 0x0005_0004, 0])[0],
0xFFFF_FFF9
);
assert_eq!(
saturated(0xFB41_3002, [9, 0x8000_0000, 0x7FFF_0000, 0x7FFF_FFFF]),
(0xBFFF_7FFF, 1)
);
}
#[test]
fn smulw_and_smlaw_keep_the_top_of_a_48_bit_product() {
const SMULWB: u32 = 0xFB31_F002;
const SMULWT: u32 = 0xFB31_F012;
const SMLAWB: u32 = 0xFB31_3002;
const SMLAWT: u32 = 0xFB31_3012;
assert_eq!(registers(SMULWB, [9, 0x1_0000, 3, 0])[0], 3);
assert_eq!(registers(SMULWT, [9, 0x1_0000, 0x0002_0000, 0])[0], 2);
assert_eq!(registers(SMULWB, [9, 0xFFFF_FFFF, 1, 0])[0], 0xFFFF_FFFF);
assert_eq!(
registers(SMULWB, [9, 0x7FFF_FFFF, 0x8000, 0])[0],
0xC000_0000
);
assert_eq!(registers(SMLAWB, [9, 0x1_0000, 3, 10])[0], 13);
assert_eq!(registers(SMLAWT, [9, 0x1_0000, 0x0002_0000, 10])[0], 12);
assert_eq!(
saturated(SMLAWB, [9, 0x1_0000, 1, 0x7FFF_FFFF]),
(0x8000_0000, 1)
);
}
#[test]
fn smmul_smmla_and_smmls_keep_the_top_word() {
assert_eq!(registers(0xFB51_F002, [9, 0x4000_0000, 4, 0])[0], 1);
assert_eq!(
registers(0xFB51_F002, [9, 0x8000_0000, 1, 0])[0],
0xFFFF_FFFF
);
assert_eq!(
registers(0xFB51_F012, [9, 0x8000_0000, 1, 0])[0],
0,
"SMMULR"
);
assert_eq!(registers(0xFB51_3002, [9, 0x4000_0000, 4, 5])[0], 6);
assert_eq!(registers(0xFB51_3002, [9, 0x8000_0000, 1, 5])[0], 4);
assert_eq!(
registers(0xFB51_3012, [9, 0x8000_0000, 1, 5])[0],
5,
"SMMLAR"
);
assert_eq!(registers(0xFB61_3002, [9, 0x4000_0000, 4, 5])[0], 4);
assert_eq!(registers(0xFB61_3002, [9, 1, 1, 0])[0], 0xFFFF_FFFF);
assert_eq!(registers(0xFB61_3012, [9, 1, 1, 0])[0], 0, "SMMLSR");
}
#[test]
fn usad8_and_usada8_sum_absolute_differences() {
let r = [9, 0x01FF_1005, 0x0300_2001, 1000];
assert_eq!(registers(0xFB71_F002, r)[0], 2 + 255 + 16 + 4);
assert_eq!(registers(0xFB71_3002, r)[0], 1000 + 277);
}
#[test]
fn smull_and_umull_write_both_halves() {
const SMULL: u32 = 0xFB82_0103; const UMULL: u32 = 0xFBA2_0103;
assert_eq!(
registers(SMULL, [9, 9, 0xFFFF_FFFE, 3]),
[0xFFFF_FFFA, 0xFFFF_FFFF, 0xFFFF_FFFE, 3]
);
assert_eq!(
registers(SMULL, [9, 9, 0x8000_0000, 0x8000_0000])[..2],
[0, 0x4000_0000]
);
assert_eq!(
registers(UMULL, [9, 9, 0xFFFF_FFFF, 0xFFFF_FFFF])[..2],
[1, 0xFFFF_FFFE]
);
}
#[test]
fn smlal_umlal_and_umaal_accumulate_64_bits() {
const SMLAL: u32 = 0xFBC2_0103;
const UMLAL: u32 = 0xFBE2_0103;
const UMAAL: u32 = 0xFBE2_0163;
assert_eq!(
registers(SMLAL, [0, 1, 0xFFFF_FFFF, 1])[..2],
[0xFFFF_FFFF, 0]
);
assert_eq!(
registers(SMLAL, [0, 0, 0xFFFF_FFFE, 3])[..2],
[0xFFFF_FFFA, 0xFFFF_FFFF]
);
assert_eq!(registers(UMLAL, [0xFFFF_FFFF, 0, 1, 1])[..2], [0, 1]);
assert_eq!(
registers(UMLAL, [0, 0, 0xFFFF_FFFF, 0xFFFF_FFFF])[..2],
[1, 0xFFFF_FFFE]
);
assert_eq!(
registers(UMAAL, [u32::MAX; 4])[..2],
[0xFFFF_FFFF, 0xFFFF_FFFF],
"the largest sum UMAAL can make just fits"
);
assert_eq!(registers(UMAAL, [5, 7, 2, 3])[..2], [18, 0]);
}
#[test]
fn smlal_by_halfwords_accumulates_a_sign_extended_product() {
let (n, m) = (0x0003_FFFE, 0xFFFF_0005);
assert_eq!(
registers(0xFBC2_0183, [0, 0, n, m])[..2],
[0xFFFF_FFF6, 0xFFFF_FFFF],
"SMLALBB"
);
assert_eq!(registers(0xFBC2_0193, [0, 0, n, m])[..2], [2, 0], "SMLALBT");
assert_eq!(
registers(0xFBC2_01A3, [0, 0, n, m])[..2],
[15, 0],
"SMLALTB"
);
assert_eq!(
registers(0xFBC2_01B3, [0, 0, n, m])[..2],
[0xFFFF_FFFD, 0xFFFF_FFFF],
"SMLALTT"
);
assert_eq!(
registers(0xFBC2_01A3, [u32::MAX, u32::MAX, n, m])[..2],
[14, 0]
);
}
#[test]
fn smlald_and_smlsld_accumulate_both_products() {
let r = [0, 1, 0x0002_0003, 0x0004_0005];
assert_eq!(registers(0xFBC2_01C3, r)[..2], [23, 1], "SMLALD");
assert_eq!(registers(0xFBC2_01D3, r)[..2], [22, 1], "SMLALDX");
assert_eq!(registers(0xFBD2_01C3, r)[..2], [7, 1], "SMLSLD");
assert_eq!(registers(0xFBD2_01D3, r)[..2], [2, 1], "SMLSLDX");
assert_eq!(
registers(0xFBD2_01C3, [0, 1, 0x0003_0002, 0x0005_0004])[..2],
[0xFFFF_FFF9, 0],
"a negative difference borrows from RdHi"
);
}
#[test]
fn sdiv_and_udiv_round_toward_zero_and_give_zero_for_zero() {
const SDIV: u32 = 0xFB91_F0F2; const UDIV: u32 = 0xFBB1_F0F2;
assert_eq!(registers(SDIV, [9, 7, 0xFFFF_FFFE, 0])[0], 0xFFFF_FFFD);
assert_eq!(registers(SDIV, [9, 0xFFFF_FFF9, 2, 0])[0], 0xFFFF_FFFD);
assert_eq!(
registers(SDIV, [9, 0x8000_0000, 0xFFFF_FFFF, 0])[0],
0x8000_0000
);
assert_eq!(registers(SDIV, [9, 5, 0, 0])[0], 0);
assert_eq!(registers(UDIV, [9, 0xFFFF_FFFF, 2, 0])[0], 0x7FFF_FFFF);
assert_eq!(registers(UDIV, [9, 7, 0xFFFF_FFFF, 0])[0], 0);
assert_eq!(registers(UDIV, [9, 7, 0, 0])[0], 0);
}
#[test]
fn a_zero_divisor_faults_only_while_ccr_traps_it() {
const MUL: u32 = 0xFB01_F302; for divide in [0xFB91_F0F4, 0xFBB1_F0F4] {
let mut bench = Bench::new(&[]);
bench.core.system.ccr |= mask(ccr::DIV_0_TRP);
bench.core.state.r[..5].copy_from_slice(&[9, 42, 6, 8, 0]);
let answer = run_block(&mut bench, &[MUL, divide]);
let state = &bench.core.state;
assert_eq!(answer, 1 << 8 | status::EXCEPTION, "{divide:#010x}");
assert_eq!((&state.r[..5], state.r[15]), (&[9, 42, 6, 252, 0][..], 4));
bench.core.state.r[4] = 5;
assert_eq!(run_block(&mut bench, &[divide]), 1 << 8 | status::NEXT);
assert_eq!(bench.core.state.r[0], 8, "{divide:#010x} 42 by 5");
bench.core.system.ccr &= !mask(ccr::DIV_0_TRP);
bench.core.state.r[4] = 0;
let answer = run_block(&mut bench, &[MUL, divide]);
let state = &bench.core.state;
assert_eq!(answer, 2 << 8 | status::NEXT, "{divide:#010x}");
assert_eq!((state.r[0], state.r[15]), (0, 8));
}
}
#[test]
fn multiply_accumulate_refuses_what_table_a5_28_leaves_out() {
for instruction in [
0xFB01_F042, 0xFB01_F082,
0xFB01_F022, 0xFB01_F012, 0xFB21_F022, 0xFB31_3022,
0xFB41_F032,
0xFB51_3022,
0xFB61_3022,
0xFB61_F002, 0xFB71_F012, 0xFB71_3022,
0xFB01_FD02, 0xFB01_FF02, 0xFB0D_F002, 0xFB0F_3002, 0xFB01_F00D, 0xFB01_F00F, 0xFB01_D002, 0xFB11_D002,
] {
assert_eq!(decoded(instruction), Err(Undefined), "{instruction:#010x}");
}
}
#[test]
fn long_multiply_divide_refuses_what_table_a5_29_leaves_out() {
for instruction in [
0xFB82_0113, 0xFB91_F002, 0xFBA1_F0F2, 0xFBB1_F002, 0xFBC2_0113, 0xFBC2_01E3, 0xFBD2_0103, 0xFBD2_0183, 0xFBE2_0113, 0xFBE2_0173, 0xFBF2_0103, 0xFB82_0003, 0xFBE2_1163,
0xFB82_D103, 0xFB82_0F03, 0xFB8D_0103, 0xFB82_010F, 0xFB91_70F2, 0xFBB1_E0F2, 0xFB91_FDF2, 0xFB9F_F0F2, 0xFBB1_F0FD, ] {
assert_eq!(decoded(instruction), Err(Undefined), "{instruction:#010x}");
}
}
#[test]
fn signed_parallel_arithmetic_sets_ge_per_lane() {
for (instruction, r, result, ge) in [
(0xFA91_F002, [0x7FFF_0001, 0x0001_FFFE], 0x8000_FFFF, 0b1100), (0xFAA1_F002, [0x0005_0003, 0x0004_0002], 0x0007_FFFF, 0b1100), (0xFAE1_F002, [0x0005_0003, 0x0004_0006], 0xFFFF_0007, 0b0011), (0xFAD1_F002, [0x0005_0003, 0x0004_0006], 0x0001_FFFD, 0b1100), (0xFA81_F002, [0x7F80_01FF, 0x01FF_0101], 0x807F_0200, 0b1011), (0xFAC1_F002, [0x0580_0010, 0x0601_0101], 0xFF7F_FF0F, 0b0001), ] {
check_parallel(instruction, r, result, Some(ge));
}
}
#[test]
fn signed_saturating_parallel_arithmetic_saturates_each_lane() {
for (instruction, r, result) in [
(0xFA91_F012, [0x7FFF_8000, 0x0001_FFFF], 0x7FFF_8000), (0xFAA1_F012, [0x0010_0020, 0x0005_0003], 0x0013_001B), (0xFAA1_F012, [0x7FFF_8000, 0x0001_0001], 0x7FFF_8000),
(0xFAE1_F012, [0x0010_0020, 0x0005_0003], 0x000D_0025), (0xFAD1_F012, [0x8000_0005, 0x0001_0007], 0x8000_FFFE), (0xFA81_F012, [0x7F80_1001, 0x01FF_1002], 0x7F80_2003), (0xFAC1_F012, [0x807F_0500, 0x01FF_0700], 0x807F_FE00), ] {
check_parallel(instruction, r, result, None);
}
}
#[test]
fn signed_halving_parallel_arithmetic_halves_each_lane() {
for (instruction, r, result) in [
(0xFA91_F022, [0x7FFF_FFFF, 0x7FFF_FFFE], 0x7FFF_FFFE), (0xFAA1_F022, [0x0010_0000, 0x0005_0003], 0x0009_FFFD), (0xFAE1_F022, [0x0010_0000, 0x0005_0003], 0x0006_0002), (0xFAD1_F022, [0x8000_0000, 0x7FFF_0001], 0x8000_FFFF), (0xFA81_F022, [0x7F80_FF03, 0x7F80_FE04], 0x7F80_FE03), (0xFAC1_F022, [0x807F_0005, 0x7F80_0102], 0x807F_FF01), ] {
check_parallel(instruction, r, result, None);
}
}
#[test]
fn unsigned_parallel_arithmetic_sets_ge_per_lane() {
for (instruction, r, result, ge) in [
(0xFA91_F042, [0xFFFF_0001, 0x0001_0002], 0x0000_0003, 0b1100), (0xFAA1_F042, [0xFFFF_0003, 0x0004_0001], 0x0000_FFFF, 0b1100), (0xFAE1_F042, [0x0003_FFFF, 0x0001_0004], 0xFFFF_0000, 0b0011), (0xFAD1_F042, [0x0005_0003, 0x0004_0006], 0x0001_FFFD, 0b1100), (0xFA81_F042, [0xFF80_0110, 0x0180_0110], 0x0000_0220, 0b1100), (0xFAC1_F042, [0x0005_8010, 0x0105_7F20], 0xFF00_01F0, 0b0110), ] {
check_parallel(instruction, r, result, Some(ge));
}
}
#[test]
fn unsigned_saturating_parallel_arithmetic_saturates_each_lane() {
for (instruction, r, result) in [
(0xFA91_F052, [0xFFFF_0001, 0x0001_0002], 0xFFFF_0003), (0xFAA1_F052, [0xFFFF_0003, 0x0004_0001], 0xFFFF_0000), (0xFAE1_F052, [0x0003_FFFF, 0x0001_0004], 0x0000_FFFF), (0xFAD1_F052, [0x0005_0003, 0x0004_0006], 0x0001_0000), (0xFA81_F052, [0xFF80_0110, 0x0180_0110], 0xFFFF_0220), (0xFAC1_F052, [0x0005_8010, 0x0105_7F20], 0x0000_0100), ] {
check_parallel(instruction, r, result, None);
}
}
#[test]
fn unsigned_halving_parallel_arithmetic_halves_each_lane() {
for (instruction, r, result) in [
(0xFA91_F062, [0xFFFF_0001, 0xFFFF_0002], 0xFFFF_0001), (0xFAA1_F062, [0xFFFF_0003, 0x0004_0001], 0x8000_FFFF), (0xFAE1_F062, [0x0003_FFFF, 0x0001_0004], 0xFFFF_8000), (0xFAD1_F062, [0x0000_FFFF, 0xFFFF_0001], 0x8000_7FFF), (0xFA81_F062, [0xFF80_0110, 0xFF80_0230], 0xFF80_0120), (0xFAC1_F062, [0x0005_8010, 0xFF05_7F20], 0x8000_00F8), ] {
check_parallel(instruction, r, result, None);
}
}
fn check_parallel(instruction: u32, [rn, rm]: [u32; 2], result: u32, ge: Option<u32>) {
for q in [0, 1] {
let bench = run(instruction, [9, rn, rm, 0], q, 0b1010);
let state = &bench.core.state;
assert_eq!(
(state.r[0], state.ge, state.q),
(result, ge.unwrap_or(0b1010), q),
"{instruction:#010x} from Q {q}"
);
}
}
#[test]
fn parallel_arithmetic_refuses_what_tables_a5_25_and_a5_26_leave_out() {
for instruction in [
0xFAB1_F002, 0xFAF1_F002, 0xFA91_F032, 0xFA91_E002, 0xFA91_FF02, 0xFA9D_F002, 0xFA91_F00F, ] {
assert_eq!(decoded(instruction), Err(Undefined), "{instruction:#010x}");
assert_eq!(
decoded(instruction | 0x40),
Err(Undefined),
"{instruction:#010x} unsigned"
);
}
}
#[test]
fn saturating_add_and_subtract_set_q_when_they_saturate() {
const QADD: u32 = 0xFA81_F082; const QDADD: u32 = 0xFA81_F092;
const QSUB: u32 = 0xFA81_F0A2;
const QDSUB: u32 = 0xFA81_F0B2;
assert_eq!(saturated(QADD, [9, 6, 5, 0]), (11, 0));
assert_eq!(saturated(QADD, [9, 0x20, 0x7FFF_FFF0, 0]), (0x7FFF_FFFF, 1));
assert_eq!(saturated(QSUB, [9, 3, 10, 0]), (7, 0));
assert_eq!(saturated(QSUB, [9, 1, 0x8000_0000, 0]), (0x8000_0000, 1));
assert_eq!(saturated(QDADD, [9, 3, 10, 0]), (16, 0));
assert_eq!(
saturated(QDADD, [9, 0x4000_0000, 0xFFFF_FFFF, 0]),
(0x7FFF_FFFE, 1),
"the doubling saturates though the sum doesn't"
);
assert_eq!(saturated(QDSUB, [9, 3, 10, 0]), (4, 0));
assert_eq!(
saturated(QDSUB, [9, 0xC000_0000, 0, 0]),
(0x7FFF_FFFF, 1),
"the doubling is exact and the difference saturates"
);
let bench = run(QADD, [9, 1, 2, 0], 1, 0);
assert_eq!(bench.core.state.q, 1, "nothing clears Q");
}
#[test]
fn reverses_and_clz_rearrange_one_register() {
const REV: u32 = 0xFA92_F082; const REV16: u32 = 0xFA92_F092;
const RBIT: u32 = 0xFA92_F0A2;
const REVSH: u32 = 0xFA92_F0B2;
const CLZ: u32 = 0xFAB2_F082;
let r = [9, 0, 0x1234_5678, 0];
assert_eq!(registers(REV, r)[0], 0x7856_3412);
assert_eq!(registers(REV16, r)[0], 0x3412_7856);
assert_eq!(registers(RBIT, r)[0], 0x1E6A_2C48);
assert_eq!(registers(RBIT, [9, 0, 1, 0])[0], 0x8000_0000);
assert_eq!(registers(REVSH, [9, 0, 0x1234_5680, 0])[0], 0xFFFF_8056);
assert_eq!(registers(REVSH, [9, 0, 0x0000_1234, 0])[0], 0x0000_3412);
for (value, zeros) in [(0, 32), (1, 31), (0x8000_0000, 0), (0x1_0000, 15)] {
assert_eq!(registers(CLZ, [9, 0, value, 0])[0], zeros, "CLZ {value:#x}");
}
}
#[test]
fn sel_picks_each_byte_by_its_ge_bit() {
const SEL: u32 = 0xFAA1_F082; let bench = run(SEL, [9, 0x1122_3344, 0xAABB_CCDD, 0], 0, 0b0101);
assert_eq!(bench.core.state.r[0], 0xAA22_CC44);
assert_eq!(bench.core.state.ge, 0b0101);
let bench = run(SEL, [9, 0x1122_3344, 0xAABB_CCDD, 0], 0, 0b1111);
assert_eq!(bench.core.state.r[0], 0x1122_3344);
}
#[test]
fn miscellaneous_refuses_what_table_a5_27_leaves_out() {
for instruction in [
0xFAA1_F092, 0xFAA1_F0B2,
0xFAB2_F092, 0xFAB2_F0A2,
0xFA93_F082, 0xFAB1_F082, 0xFA81_E082, 0xFA81_FD82, 0xFA8F_F082, 0xFA81_F08F, 0xFA9D_F08D, ] {
assert_eq!(decoded(instruction), Err(Undefined), "{instruction:#010x}");
}
}
}