use std::mem::offset_of;
use cranelift_codegen::ir::{self, InstBuilder, MemFlagsData, Value, condcodes::IntCC, types};
use rustc_apfloat::{FloatConvert, Round};
use super::super::family::{Family, Position, offsets};
use super::super::fp::{self, Bits, Fixed};
use super::super::registers::{control, cpacr, fpccr, fpscr, is_set, mask};
use super::super::state::State;
use super::{
Attributes, Emitter, FAULTED, Fault, Flag, KEEP_PC, Size, Translated, Undefined, bits,
};
use crate::emulator::core::Fma;
use crate::machine::armv7m::Fpu;
use crate::soc::Endian;
pub(super) fn coprocessor(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let mut decoded = decode(hw1, hw2, e.features.fpu);
if bit(hw1, 12) == 1 && e.in_it() && matches!(decoded, Decoded::Operation(_)) {
decoded = Decoded::Undefined;
}
match decoded {
Decoded::Unallocated => Err(Undefined),
Decoded::Absent => {
e.raise(Fault::NoCoprocessor);
Ok(())
}
Decoded::Undefined => {
check(e, false);
Err(Undefined)
}
Decoded::Operation(operation) => {
check(e, true);
emit(e, operation)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Decoded {
Unallocated,
Absent,
Undefined,
Operation(Operation),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Precision {
Single,
Double,
}
impl Precision {
fn words(self) -> u32 {
match self {
Self::Single => 1,
Self::Double => 2,
}
}
fn register(self, x: u32, vx: u32) -> u32 {
match self {
Self::Single => single_register(vx, x),
Self::Double => 2 * (x << 4 | vx),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Rounding {
Fpscr,
Fixed(Round),
}
const MODES: [Round; 5] = [
Round::NearestTiesToEven,
Round::TowardPositive,
Round::TowardNegative,
Round::TowardZero,
Round::NearestTiesToAway,
];
impl Rounding {
fn code(self) -> u32 {
match self {
Self::Fpscr => 0,
Self::Fixed(mode) => {
let index = MODES.iter().position(|&each| each == mode);
1 + u32::try_from(index.expect("one of the modes")).expect("five")
}
}
}
fn mode(code: u32, fpscr: u32) -> Round {
match code.checked_sub(1) {
Some(index) => MODES[index as usize],
None => fp::rounding(fpscr),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Operation {
Memory(Transfer),
Core {
to_core: bool,
t: u32,
s: u32,
},
CorePair {
to_core: bool,
t: u32,
t2: u32,
s: u32,
},
ReadStatus {
t: u32,
},
WriteStatus {
t: u32,
},
Constant {
d: u32,
value: u64,
precision: Precision,
},
Move {
d: u32,
m: u32,
sign: Sign,
precision: Precision,
},
Arithmetic {
operation: Arithmetic,
d: u32,
n: u32,
m: u32,
precision: Precision,
},
Compare {
d: u32,
m: Option<u32>,
quiet_nan_exc: bool,
precision: Precision,
},
ToFixed {
d: u32,
m: u32,
format: Fixed,
rounding: Rounding,
source: Precision,
result: Precision,
},
FromFixed {
d: u32,
m: u32,
format: Fixed,
rounding: Rounding,
result: Precision,
},
ToHalf {
d: u32,
m: u32,
top: bool,
source: Precision,
},
FromHalf {
d: u32,
m: u32,
top: bool,
result: Precision,
},
Convert {
d: u32,
m: u32,
result: Precision,
},
RoundToIntegral {
d: u32,
m: u32,
rounding: Rounding,
exact: bool,
precision: Precision,
},
Extremum {
maximum: bool,
d: u32,
n: u32,
m: u32,
precision: Precision,
},
Select {
d: u32,
n: u32,
m: u32,
cond: u32,
precision: Precision,
},
BeyondBank,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Transfer {
load: bool,
d: u32,
n: u32,
offset: i32,
writeback: Option<i32>,
words: u32,
double: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Sign {
Keep,
Clear,
Flip,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
enum Arithmetic {
MultiplyAccumulate,
MultiplySubtract,
NegateMultiplyAccumulate,
NegateMultiplySubtract,
Multiply,
NegateMultiply,
Add,
Subtract,
Divide,
FusedMultiplyAccumulate,
FusedMultiplySubtract,
FusedNegateMultiplyAccumulate,
FusedNegateMultiplySubtract,
SquareRoot,
}
impl Arithmetic {
const ALL: [Self; 14] = [
Self::MultiplyAccumulate,
Self::MultiplySubtract,
Self::NegateMultiplyAccumulate,
Self::NegateMultiplySubtract,
Self::Multiply,
Self::NegateMultiply,
Self::Add,
Self::Subtract,
Self::Divide,
Self::FusedMultiplyAccumulate,
Self::FusedMultiplySubtract,
Self::FusedNegateMultiplyAccumulate,
Self::FusedNegateMultiplySubtract,
Self::SquareRoot,
];
fn evaluate<B: Bits>(self, sd: B, sn: B, sm: B, fpscr: &mut u32) -> B {
use fp::{add, divide, multiply, multiply_add, negate, square_root, subtract};
match self {
Self::MultiplyAccumulate => {
let product = multiply(sn, sm, fpscr);
add(sd, product, fpscr)
}
Self::MultiplySubtract => {
let product = multiply(sn, sm, fpscr);
add(sd, negate(product), fpscr)
}
Self::NegateMultiplyAccumulate => {
let product = multiply(sn, sm, fpscr);
add(negate(sd), negate(product), fpscr)
}
Self::NegateMultiplySubtract => {
let product = multiply(sn, sm, fpscr);
add(negate(sd), product, fpscr)
}
Self::Multiply => multiply(sn, sm, fpscr),
Self::NegateMultiply => negate(multiply(sn, sm, fpscr)),
Self::Add => add(sn, sm, fpscr),
Self::Subtract => subtract(sn, sm, fpscr),
Self::Divide => divide(sn, sm, fpscr),
Self::FusedMultiplyAccumulate => multiply_add(sd, sn, sm, fpscr),
Self::FusedMultiplySubtract => multiply_add(sd, negate(sn), sm, fpscr),
Self::FusedNegateMultiplyAccumulate => multiply_add(negate(sd), negate(sn), sm, fpscr),
Self::FusedNegateMultiplySubtract => multiply_add(negate(sd), sn, sm, fpscr),
Self::SquareRoot => square_root(sm, fpscr),
}
}
fn fused(self) -> bool {
matches!(
self,
Self::FusedMultiplyAccumulate
| Self::FusedMultiplySubtract
| Self::FusedNegateMultiplyAccumulate
| Self::FusedNegateMultiplySubtract
)
}
}
fn decode(hw1: u32, hw2: u32, fpu: Fpu) -> Decoded {
let op1 = bits(hw1, 9, 4);
if op1 >> 1 == 0 || op1 >> 4 == 0b11 {
return Decoded::Unallocated;
}
if bits(hw2, 11, 9) != 0b101 {
return Decoded::Absent;
}
match decode_fp(hw1, hw2, op1, fpu) {
Ok(operation) => Decoded::Operation(operation),
Err(Undefined) => Decoded::Undefined,
}
}
fn decode_fp(hw1: u32, hw2: u32, op1: u32, fpu: Fpu) -> Result<Operation, Undefined> {
let unconditional = bit(hw1, 12) == 1;
match (op1 >> 5, bit(hw2, 4)) {
(1, 0) if unconditional => unconditional_data_processing(hw1, hw2, fpu).map(within_bank),
(1, 0) => data_processing(hw1, hw2, fpu).map(within_bank),
_ if unconditional => Err(Undefined),
(0, _) if op1 >> 1 == 0b00010 => transfer64(hw1, hw2),
(0, _) => load_store(hw1, hw2),
_ => transfer32(hw1, hw2),
}
}
fn load_store(hw1: u32, hw2: u32) -> Result<Operation, Undefined> {
let (p, u, w) = (bit(hw1, 8), bit(hw1, 7), bit(hw1, 5));
let load = bit(hw1, 4) == 1;
let n = bits(hw1, 3, 0);
let double = bit(hw2, 8) == 1;
let imm8 = bits(hw2, 7, 0);
let imm32 = (imm8 << 2).cast_signed();
let down = if u == 1 { imm32 } else { -imm32 };
if p == 1 && w == 0 {
if !load && n == 15 {
return Err(Undefined);
}
let d = if double {
double_register(bit(hw1, 6), bits(hw2, 15, 12))
} else {
Some(single_register(bits(hw2, 15, 12), bit(hw1, 6)))
};
let Some(d) = d else {
return Ok(Operation::BeyondBank);
};
return Ok(Operation::Memory(Transfer {
load,
d,
n,
offset: down,
writeback: None,
words: if double { 2 } else { 1 },
double,
}));
}
if p == u || n == 15 {
return Err(Undefined);
}
let (d, words) = if double {
let (first, registers) = (bit(hw1, 6) << 4 | bits(hw2, 15, 12), imm8 / 2);
if registers == 0 || first + registers > 16 {
return Err(Undefined);
}
(2 * first, 2 * registers)
} else {
let first = single_register(bits(hw2, 15, 12), bit(hw1, 6));
if imm8 == 0 || first + imm8 > 32 {
return Err(Undefined);
}
(first, imm8)
};
Ok(Operation::Memory(Transfer {
load,
d,
n,
offset: if u == 1 { 0 } else { -imm32 },
writeback: (w == 1).then_some(down),
words,
double,
}))
}
fn transfer64(hw1: u32, hw2: u32) -> Result<Operation, Undefined> {
let to_core = bit(hw1, 4) == 1;
let (t, t2) = (bits(hw2, 15, 12), bits(hw1, 3, 0));
if bits(hw2, 7, 6) != 0 || bit(hw2, 4) != 1 {
return Err(Undefined);
}
if unpredictable_core(t) || unpredictable_core(t2) || (to_core && t == t2) {
return Err(Undefined);
}
if bit(hw2, 8) == 1 {
return Ok(double_register(bit(hw2, 5), bits(hw2, 3, 0)).map_or(
Operation::BeyondBank,
|s| Operation::CorePair { to_core, t, t2, s },
));
}
let s = single_register(bits(hw2, 3, 0), bit(hw2, 5));
if s == 31 {
return Err(Undefined);
}
Ok(Operation::CorePair { to_core, t, t2, s })
}
fn transfer32(hw1: u32, hw2: u32) -> Result<Operation, Undefined> {
let to_core = bit(hw1, 4) == 1;
let t = bits(hw2, 15, 12);
if bits(hw2, 6, 5) != 0 || bits(hw2, 3, 0) != 0 {
return Err(Undefined);
}
let s = match (bit(hw2, 8), bits(hw1, 7, 5)) {
(0, 0b111) => {
if bits(hw1, 3, 0) != 0b0001 || bit(hw2, 7) != 0 || t == 13 || (t == 15 && !to_core) {
return Err(Undefined);
}
return Ok(if to_core {
Operation::ReadStatus { t }
} else {
Operation::WriteStatus { t }
});
}
(0, 0b000) => Some(single_register(bits(hw1, 3, 0), bit(hw2, 7))),
(1, 0b000 | 0b001) => {
double_register(bit(hw2, 7), bits(hw1, 3, 0)).map(|s| s + bit(hw1, 5))
}
_ => return Err(Undefined),
};
if unpredictable_core(t) {
return Err(Undefined);
}
Ok(s.map_or(Operation::BeyondBank, |s| Operation::Core { to_core, t, s }))
}
fn precision(hw2: u32, fpu: Fpu) -> Result<Precision, Undefined> {
match (bit(hw2, 8), fpu) {
(0, _) => Ok(Precision::Single),
(_, Fpu::FpV5Dp) => Ok(Precision::Double),
_ => Err(Undefined),
}
}
fn data_processing(hw1: u32, hw2: u32, fpu: Fpu) -> Result<Operation, Undefined> {
let precision = precision(hw2, fpu)?;
let d = precision.register(bit(hw1, 6), bits(hw2, 15, 12));
let n = precision.register(bit(hw2, 7), bits(hw1, 3, 0));
let m = precision.register(bit(hw2, 5), bits(hw2, 3, 0));
let op = bit(hw2, 6) == 1;
let operation = match (bits(hw1, 7, 4) & 0b1011, op) {
(0b0000, false) => Arithmetic::MultiplyAccumulate,
(0b0000, true) => Arithmetic::MultiplySubtract,
(0b0001, true) => Arithmetic::NegateMultiplyAccumulate,
(0b0001, false) => Arithmetic::NegateMultiplySubtract,
(0b0010, false) => Arithmetic::Multiply,
(0b0010, true) => Arithmetic::NegateMultiply,
(0b0011, false) => Arithmetic::Add,
(0b0011, true) => Arithmetic::Subtract,
(0b1000, false) => Arithmetic::Divide,
(0b1001, true) => Arithmetic::FusedNegateMultiplyAccumulate,
(0b1001, false) => Arithmetic::FusedNegateMultiplySubtract,
(0b1010, false) => Arithmetic::FusedMultiplyAccumulate,
(0b1010, true) => Arithmetic::FusedMultiplySubtract,
(0b1011, false) => {
if bit(hw2, 7) != 0 || bit(hw2, 5) != 0 {
return Err(Undefined);
}
let imm8 = bits(hw1, 3, 0) << 4 | bits(hw2, 3, 0);
let value = expand_imm(imm8, precision);
return Ok(Operation::Constant {
d,
value,
precision,
});
}
(0b1011, true) => return other_data_processing(hw1, hw2, precision, fpu),
_ => return Err(Undefined),
};
Ok(Operation::Arithmetic {
operation,
d,
n,
m,
precision,
})
}
fn other_data_processing(
hw1: u32,
hw2: u32,
precision: Precision,
fpu: Fpu,
) -> Result<Operation, Undefined> {
let high = bit(hw2, 7) == 1;
let opc2 = bits(hw1, 3, 0);
let fpv5 = matches!(fpu, Fpu::FpV5Sp | Fpu::FpV5Dp);
let (dx, dv, mx, mv) = (bit(hw1, 6), bits(hw2, 15, 12), bit(hw2, 5), bits(hw2, 3, 0));
let (d, m) = (precision.register(dx, dv), precision.register(mx, mv));
let single_d = Precision::Single.register(dx, dv);
let single_m = Precision::Single.register(mx, mv);
Ok(match opc2 {
0b0000 => Operation::Move {
d,
m,
sign: if high { Sign::Clear } else { Sign::Keep },
precision,
},
0b0001 if high => Operation::Arithmetic {
operation: Arithmetic::SquareRoot,
d,
n: m,
m,
precision,
},
0b0001 => Operation::Move {
d,
m,
sign: Sign::Flip,
precision,
},
0b0010 => Operation::FromHalf {
d,
m: single_m,
top: high,
result: precision,
},
0b0011 => Operation::ToHalf {
d: single_d,
m,
top: high,
source: precision,
},
0b0101 if bit(hw2, 5) != 0 || bits(hw2, 3, 0) != 0 => return Err(Undefined),
0b0100 | 0b0101 => Operation::Compare {
d,
m: (opc2 == 0b0100).then_some(m),
quiet_nan_exc: high,
precision,
},
0b0110 if fpv5 => Operation::RoundToIntegral {
d,
m,
rounding: if high {
Rounding::Fixed(Round::TowardZero)
} else {
Rounding::Fpscr
},
exact: false,
precision,
},
0b0111 if fpv5 && !high => Operation::RoundToIntegral {
d,
m,
rounding: Rounding::Fpscr,
exact: true,
precision,
},
0b0111 if high && fpu == Fpu::FpV5Dp => match precision {
Precision::Double => Operation::Convert {
d: single_d,
m,
result: Precision::Single,
},
Precision::Single => Operation::Convert {
d: Precision::Double.register(dx, dv),
m: single_m,
result: Precision::Double,
},
},
0b1000 => Operation::FromFixed {
d,
m: single_m,
format: integer(high),
rounding: Rounding::Fpscr,
result: precision,
},
0b1100 | 0b1101 => Operation::ToFixed {
d: single_d,
m,
format: integer(opc2 & 1 == 1),
rounding: if high {
Rounding::Fixed(Round::TowardZero)
} else {
Rounding::Fpscr
},
source: precision,
result: Precision::Single,
},
0b1010 | 0b1011 | 0b1110 | 0b1111 => fixed_point(hw2, opc2, d, precision)?,
_ => return Err(Undefined),
})
}
fn fixed_point(hw2: u32, opc2: u32, d: u32, precision: Precision) -> Result<Operation, Undefined> {
let size: u32 = if bit(hw2, 7) == 1 { 32 } else { 16 };
let encoded = bits(hw2, 3, 0) << 1 | bit(hw2, 5);
let fraction_bits = size.checked_sub(encoded).ok_or(Undefined)?;
let format = Fixed {
size,
fraction_bits,
signed: opc2 & 1 == 0,
};
Ok(if opc2 & 0b0100 != 0 {
Operation::ToFixed {
d,
m: d,
format,
rounding: Rounding::Fixed(Round::TowardZero),
source: precision,
result: precision,
}
} else {
Operation::FromFixed {
d,
m: d,
format,
rounding: Rounding::Fixed(Round::NearestTiesToEven),
result: precision,
}
})
}
fn unconditional_data_processing(hw1: u32, hw2: u32, fpu: Fpu) -> Result<Operation, Undefined> {
if !matches!(fpu, Fpu::FpV5Sp | Fpu::FpV5Dp) {
return Err(Undefined);
}
let precision = precision(hw2, fpu)?;
let d = precision.register(bit(hw1, 6), bits(hw2, 15, 12));
let n = precision.register(bit(hw2, 7), bits(hw1, 3, 0));
let m = precision.register(bit(hw2, 5), bits(hw2, 3, 0));
let op = bit(hw2, 6);
if bit(hw1, 7) == 0 {
if op != 0 {
return Err(Undefined);
}
let cc = bits(hw1, 5, 4);
let cond = cc << 2 | (bit(cc, 1) ^ bit(cc, 0)) << 1;
return Ok(Operation::Select {
d,
n,
m,
cond,
precision,
});
}
match bits(hw1, 5, 4) {
0b00 => Ok(Operation::Extremum {
maximum: op == 0,
d,
n,
m,
precision,
}),
0b11 => directed(hw1, hw2, precision),
_ => Err(Undefined),
}
}
fn directed(hw1: u32, hw2: u32, precision: Precision) -> Result<Operation, Undefined> {
let mode = match bits(hw1, 1, 0) {
0b00 => Round::NearestTiesToAway,
0b01 => Round::NearestTiesToEven,
0b10 => Round::TowardPositive,
_ => Round::TowardNegative,
};
let rounding = Rounding::Fixed(mode);
let (dx, dv) = (bit(hw1, 6), bits(hw2, 15, 12));
let m = precision.register(bit(hw2, 5), bits(hw2, 3, 0));
match (bits(hw1, 3, 2), bits(hw2, 7, 6)) {
(0b10, 0b01) => Ok(Operation::RoundToIntegral {
d: precision.register(dx, dv),
m,
rounding,
exact: false,
precision,
}),
(0b11, 0b01 | 0b11) => Ok(Operation::ToFixed {
d: Precision::Single.register(dx, dv),
m,
format: integer(bit(hw2, 7) == 1),
rounding,
source: precision,
result: Precision::Single,
}),
_ => Err(Undefined),
}
}
fn within_bank(operation: Operation) -> Operation {
let registers = match operation {
Operation::Constant { d, .. } => [d, d, d],
Operation::Move { d, m, .. }
| Operation::ToFixed { d, m, .. }
| Operation::FromFixed { d, m, .. }
| Operation::ToHalf { d, m, .. }
| Operation::FromHalf { d, m, .. }
| Operation::Convert { d, m, .. }
| Operation::RoundToIntegral { d, m, .. } => [d, m, m],
Operation::Compare { d, m, .. } => [d, m.unwrap_or(d), d],
Operation::Arithmetic { d, n, m, .. }
| Operation::Extremum { d, n, m, .. }
| Operation::Select { d, n, m, .. } => [d, n, m],
_ => [0; 3],
};
if registers.iter().any(|®ister| register >= 32) {
Operation::BeyondBank
} else {
operation
}
}
fn integer(signed: bool) -> Fixed {
Fixed {
size: 32,
fraction_bits: 0,
signed,
}
}
fn expand_imm(imm8: u32, precision: Precision) -> u64 {
let (exponent_bits, fraction_bits) = match precision {
Precision::Single => (8, 23),
Precision::Double => (11, 52),
};
let b = u64::from(bit(imm8, 6));
let replicated = ((1 << (exponent_bits - 3)) - 1) * b;
let exponent = (b ^ 1) << (exponent_bits - 1) | replicated << 2 | u64::from(bits(imm8, 5, 4));
let sign = u64::from(bit(imm8, 7)) << (exponent_bits + fraction_bits);
sign | exponent << fraction_bits | u64::from(bits(imm8, 3, 0)) << (fraction_bits - 4)
}
fn single_register(vx: u32, x: u32) -> u32 {
vx << 1 | x
}
fn double_register(x: u32, vx: u32) -> Option<u32> {
(x == 0).then_some(2 * vx)
}
fn unpredictable_core(t: u32) -> bool {
t == 13 || t == 15
}
fn bit(value: u32, n: u32) -> u32 {
bits(value, n, n)
}
fn check(e: &mut Emitter<'_, '_>, execute: bool) {
if e.fp_checked {
return;
}
e.fp_checked = execute;
let cp10 = i64::from(mask(cpacr::CP10));
let cpacr = e.load_core(offsets::CPACR);
let access = e.b.ins().band_imm_s(cpacr, cp10);
let full = e.b.ins().icmp_imm_s(IntCC::Equal, access, cp10);
let fpccr = e.load_core(offsets::FPCCR);
let lazy = e.b.ins().band_imm_s(fpccr, i64::from(mask(fpccr::LSPACT)));
let settled = e.b.ins().icmp_imm_s(IntCC::Equal, lazy, 0);
let control = e.load_field(offset_of!(State, control));
let context =
e.b.ins()
.band_imm_s(control, i64::from(mask(control::FPCA)));
let owned = e.b.ins().icmp_imm_s(IntCC::NotEqual, context, 0);
let ready = e.b.ins().band(full, settled);
let ready = e.b.ins().band(ready, owned);
let slow = e.b.create_block();
e.b.set_cold_block(slow);
let done = e.b.create_block();
e.b.ins().brif(ready, done, &[], slow, &[]);
e.b.seal_block(slow);
e.b.switch_to_block(slow);
e.accessed = true;
let execute = e.constant(u32::from(execute));
let pc = e.constant(e.pc());
let index = e.constant(e.index);
let answer = e.call_helper(
execute_fp_check as *const () as usize,
&[execute, pc, index],
types::I64,
);
e.fault_unless(answer);
e.b.ins().jump(done, &[]);
e.b.seal_block(done);
e.b.switch_to_block(done);
}
fn emit(e: &mut Emitter<'_, '_>, operation: Operation) -> Translated {
match operation {
Operation::BeyondBank => return Err(Undefined),
Operation::Memory(transfer) => memory(e, transfer),
Operation::Core { to_core, t, s } => {
if to_core {
let value = load_single(e, s);
e.set_reg(t, value);
} else {
let value = e.reg(t);
store_single(e, s, value);
}
}
Operation::CorePair { to_core, t, t2, s } => {
if to_core {
let (low, high) = (load_single(e, s), load_single(e, s + 1));
e.set_reg(t, low);
e.set_reg(t2, high);
} else {
let (low, high) = (e.reg(t), e.reg(t2));
store_single(e, s, low);
store_single(e, s + 1, high);
}
}
Operation::ReadStatus { t } => read_status(e, t),
Operation::WriteStatus { t } => {
let value = e.reg(t);
let value = e.b.ins().band_imm_s(value, i64::from(fp::FPSCR_WRITABLE));
e.store_field(offset_of!(State, fpscr), value);
}
Operation::Constant {
d,
value,
precision,
} => {
let words = halves(value);
for (register, &word) in (d..).zip(&words[..precision.words() as usize]) {
let word = e.constant(word);
store_single(e, register, word);
}
}
Operation::Move {
d,
m,
sign,
precision,
} => move_register(e, (d, m), sign, precision),
Operation::Select {
d,
n,
m,
cond,
precision,
} => select(e, (d, n, m), cond, precision),
Operation::Arithmetic {
operation,
d,
n,
m,
precision: Precision::Single,
} if e.fma == Fma::Instruction || !operation.fused() => {
let slow = e.b.create_block();
e.b.set_cold_block(slow);
let done = e.b.create_block();
let result = host_arithmetic(e, operation, [d, n, m], slow);
store_single(e, d, result);
e.b.ins().jump(done, &[]);
e.b.seal_block(slow);
e.b.switch_to_block(slow);
let function = arithmetic::<u32> as *const () as usize;
call(e, function, &[operation as u32, d, n, m]);
e.b.ins().jump(done, &[]);
e.b.seal_block(done);
e.b.switch_to_block(done);
}
_ => {
let (function, arguments) =
helper(operation).expect("an operation the IR doesn't run has a helper");
call(e, function, &arguments);
}
}
Ok(())
}
fn move_register(e: &mut Emitter<'_, '_>, (d, m): (u32, u32), sign: Sign, precision: Precision) {
let mut values: Vec<Value> = (m..m + precision.words())
.map(|register| load_single(e, register))
.collect();
let top = values.last_mut().expect("a register has a word");
let sign_bit = i64::from(fp::SIGN);
*top = match sign {
Sign::Keep => *top,
Sign::Clear => e.b.ins().band_imm_s(*top, !sign_bit),
Sign::Flip => e.b.ins().bxor_imm_s(*top, sign_bit),
};
for (register, value) in (d..).zip(values) {
store_single(e, register, value);
}
}
fn select(e: &mut Emitter<'_, '_>, (d, n, m): (u32, u32, u32), cond: u32, precision: Precision) {
let holds = e.condition(cond);
let chosen: Vec<Value> = (0..precision.words())
.map(|word| {
let (taken, other) = (load_single(e, n + word), load_single(e, m + word));
e.b.ins().select(holds, taken, other)
})
.collect();
for (register, value) in (d..).zip(chosen) {
store_single(e, register, value);
}
}
fn helper(operation: Operation) -> Option<(usize, Vec<u32>)> {
arithmetic_helper(operation).or_else(|| conversion_helper(operation))
}
fn by_precision(precision: Precision, single: usize, double: usize) -> usize {
match precision {
Precision::Single => single,
Precision::Double => double,
}
}
fn arithmetic_helper(operation: Operation) -> Option<(usize, Vec<u32>)> {
Some(match operation {
Operation::Arithmetic {
operation,
d,
n,
m,
precision,
} => (
by_precision(
precision,
arithmetic::<u32> as *const () as usize,
arithmetic::<u64> as *const () as usize,
),
vec![operation as u32, d, n, m],
),
Operation::Extremum {
maximum,
d,
n,
m,
precision,
} => (
by_precision(
precision,
extremum::<u32> as *const () as usize,
extremum::<u64> as *const () as usize,
),
vec![d, n, m, u32::from(maximum)],
),
Operation::Compare {
d,
m,
quiet_nan_exc,
precision,
} => (
by_precision(
precision,
compare::<u32> as *const () as usize,
compare::<u64> as *const () as usize,
),
vec![
d,
m.unwrap_or(d),
u32::from(m.is_none()),
u32::from(quiet_nan_exc),
],
),
Operation::RoundToIntegral {
d,
m,
rounding,
exact,
precision,
} => (
by_precision(
precision,
round_to_integral::<u32> as *const () as usize,
round_to_integral::<u64> as *const () as usize,
),
vec![d, m, rounding.code(), u32::from(exact)],
),
_ => return None,
})
}
fn conversion_helper(operation: Operation) -> Option<(usize, Vec<u32>)> {
Some(match operation {
Operation::ToFixed {
d,
m,
format,
rounding,
source,
result,
} => {
let from_double = by_precision(
result,
to_fixed::<u64, u32> as *const () as usize,
to_fixed::<u64, u64> as *const () as usize,
);
(
by_precision(
source,
to_fixed::<u32, u32> as *const () as usize,
from_double,
),
vec![d, m, pack(format), rounding.code()],
)
}
Operation::FromFixed {
d,
m,
format,
rounding,
result,
} => (
by_precision(
result,
from_fixed::<u32> as *const () as usize,
from_fixed::<u64> as *const () as usize,
),
vec![d, m, pack(format), rounding.code()],
),
Operation::ToHalf { d, m, top, source } => (
by_precision(
source,
to_half::<u32> as *const () as usize,
to_half::<u64> as *const () as usize,
),
vec![d, m, u32::from(top)],
),
Operation::FromHalf { d, m, top, result } => (
by_precision(
result,
from_half::<u32> as *const () as usize,
from_half::<u64> as *const () as usize,
),
vec![d, m, u32::from(top)],
),
Operation::Convert { d, m, result } => (
by_precision(
result,
convert::<u64, u32> as *const () as usize,
convert::<u32, u64> as *const () as usize,
),
vec![d, m],
),
_ => return None,
})
}
fn halves(value: u64) -> [u32; 2] {
[value, value >> 32].map(|word| u32::try_from(word & u64::from(u32::MAX)).expect("masked"))
}
fn memory(e: &mut Emitter<'_, '_>, transfer: Transfer) {
let Transfer {
load,
d,
n,
offset,
writeback,
words,
double,
} = transfer;
let swapped = double && e.features.endian == Endian::Big;
let register = |word: u32| d + if swapped { word ^ 1 } else { word };
let base = if n == 15 {
e.constant(e.pc().wrapping_add(4) & !3)
} else {
e.reg(n)
};
let start = e.b.ins().iadd_imm_s(base, i64::from(offset));
let address =
|e: &mut Emitter<'_, '_>, word: u32| e.b.ins().iadd_imm_s(start, i64::from(4 * word));
if load {
let values: Vec<Value> = (0..words)
.map(|word| {
let at = address(e, word);
e.load(at, Size::Word, Attributes::ALIGNED)
})
.collect();
for (word, value) in (0..).zip(values) {
store_single(e, register(word), value);
}
} else {
for word in 0..words {
let at = address(e, word);
let value = load_single(e, register(word));
e.store(at, value, Size::Word, Attributes::ALIGNED);
}
}
if let Some(amount) = writeback {
let value = e.b.ins().iadd_imm_s(base, i64::from(amount));
e.set_reg(n, value);
}
}
fn read_status(e: &mut Emitter<'_, '_>, t: u32) {
let fpscr = e.load_field(offset_of!(State, fpscr));
if t != 15 {
e.set_reg(t, fpscr);
return;
}
for (flag, field) in [
(Flag::N, fpscr::N),
(Flag::Z, fpscr::Z),
(Flag::C, fpscr::C),
(Flag::V, fpscr::V),
] {
let bit = e.b.ins().band_imm_s(fpscr, i64::from(mask(field)));
let value = e.b.ins().ushr_imm_u(bit, i64::from(field.bit));
e.set_flag(flag, value);
}
}
fn load_single(e: &mut Emitter<'_, '_>, s: u32) -> Value {
e.load_field(offset_of!(State, s) + 4 * s as usize)
}
fn store_single(e: &mut Emitter<'_, '_>, s: u32, value: Value) {
e.store_field(offset_of!(State, s) + 4 * s as usize, value);
}
const FPSCR_MODE_AND_INEXACT: u32 =
mask(fpscr::AHP) | mask(fpscr::DN) | mask(fpscr::FZ) | mask(fpscr::RMODE) | mask(fpscr::IXC);
const SMALLEST_NORMAL: u32 = 0x0080_0000;
fn host_arithmetic(
e: &mut Emitter<'_, '_>,
operation: Arithmetic,
[d, n, m]: [u32; 3],
slow: ir::Block,
) -> Value {
let fpscr = e.load_field(offset_of!(State, fpscr));
let mode =
e.b.ins()
.band_imm_s(fpscr, i64::from(FPSCR_MODE_AND_INEXACT));
let mut on_host =
e.b.ins()
.icmp_imm_s(IntCC::Equal, mode, i64::from(mask(fpscr::IXC)));
let (sd, sn, sm) = (load_single(e, d), load_single(e, n), load_single(e, m));
let operands = match operation {
Arithmetic::SquareRoot => vec![sm],
Arithmetic::Add
| Arithmetic::Subtract
| Arithmetic::Multiply
| Arithmetic::NegateMultiply
| Arithmetic::Divide => vec![sn, sm],
_ => vec![sd, sn, sm],
};
for operand in operands {
let magnitude = e.b.ins().band_imm_s(operand, i64::from(!fp::SIGN));
let zero = e.b.ins().icmp_imm_s(IntCC::Equal, magnitude, 0);
let normal = e.b.ins().icmp_imm_s(
IntCC::UnsignedGreaterThanOrEqual,
magnitude,
i64::from(SMALLEST_NORMAL),
);
let usable = e.b.ins().bor(zero, normal);
on_host = e.b.ins().band(on_host, usable);
}
let fast = e.b.create_block();
e.b.ins().brif(on_host, fast, &[], slow, &[]);
e.b.seal_block(fast);
e.b.switch_to_block(fast);
let float = |e: &mut Emitter<'_, '_>, bits: Value| {
e.b.ins().bitcast(types::F32, MemFlagsData::new(), bits)
};
let (sd, sn, sm) = (float(e, sd), float(e, sn), float(e, sm));
let mut rounded = Vec::new();
let mut product = |e: &mut Emitter<'_, '_>| {
let product = e.b.ins().fmul(sn, sm);
rounded.push(product);
product
};
let result = match operation {
Arithmetic::Add => e.b.ins().fadd(sn, sm),
Arithmetic::Subtract => e.b.ins().fsub(sn, sm),
Arithmetic::Multiply => e.b.ins().fmul(sn, sm),
Arithmetic::NegateMultiply => {
let product = e.b.ins().fmul(sn, sm);
e.b.ins().fneg(product)
}
Arithmetic::Divide => e.b.ins().fdiv(sn, sm),
Arithmetic::SquareRoot => e.b.ins().sqrt(sm),
Arithmetic::MultiplyAccumulate => {
let product = product(e);
e.b.ins().fadd(sd, product)
}
Arithmetic::MultiplySubtract => {
let product = product(e);
e.b.ins().fsub(sd, product)
}
Arithmetic::NegateMultiplyAccumulate => {
let product = product(e);
let sum = e.b.ins().fadd(sd, product);
e.b.ins().fneg(sum)
}
Arithmetic::NegateMultiplySubtract => {
let product = product(e);
e.b.ins().fsub(product, sd)
}
Arithmetic::FusedMultiplyAccumulate => e.b.ins().fma(sn, sm, sd),
Arithmetic::FusedMultiplySubtract => {
let sn = e.b.ins().fneg(sn);
e.b.ins().fma(sn, sm, sd)
}
Arithmetic::FusedNegateMultiplyAccumulate => {
let sn = e.b.ins().fneg(sn);
let sd = e.b.ins().fneg(sd);
e.b.ins().fma(sn, sm, sd)
}
Arithmetic::FusedNegateMultiplySubtract => {
let sd = e.b.ins().fneg(sd);
e.b.ins().fma(sn, sm, sd)
}
};
rounded.push(result);
let mut normal = None;
for value in rounded {
let bits = e.b.ins().bitcast(types::I32, MemFlagsData::new(), value);
let exponent = e.b.ins().ushr_imm_u(bits, 23);
let exponent = e.b.ins().band_imm_s(exponent, 0xFF);
let low =
e.b.ins()
.icmp_imm_s(IntCC::UnsignedGreaterThanOrEqual, exponent, 2);
let high =
e.b.ins()
.icmp_imm_s(IntCC::UnsignedLessThan, exponent, 0xFF);
let this = e.b.ins().band(low, high);
normal = Some(normal.map_or(this, |normal| e.b.ins().band(normal, this)));
}
let normal = normal.expect("a result");
let keep = e.b.create_block();
e.b.ins().brif(normal, keep, &[], slow, &[]);
e.b.seal_block(keep);
e.b.switch_to_block(keep);
e.b.ins().bitcast(types::I32, MemFlagsData::new(), result)
}
fn call(e: &mut Emitter<'_, '_>, function: usize, arguments: &[u32]) {
let arguments: Vec<Value> = arguments
.iter()
.map(|&argument| e.constant(argument))
.collect();
e.call_plain(function, &arguments);
}
fn pack(format: Fixed) -> u32 {
format.size | format.fraction_bits << 8 | u32::from(format.signed) << 16
}
fn unpack(packed: u32) -> Fixed {
Fixed {
size: packed & 0xFF,
fraction_bits: packed >> 8 & 0xFF,
signed: packed >> 16 != 0,
}
}
trait Register: Bits + Default {
fn read(state: &State, s: u32) -> Self;
fn write(self, state: &mut State, s: u32);
fn extend(word: u32, signed: bool) -> Self;
}
impl Register for u32 {
fn read(state: &State, s: u32) -> Self {
state.s[s as usize]
}
fn write(self, state: &mut State, s: u32) {
state.s[s as usize] = self;
}
fn extend(word: u32, _signed: bool) -> Self {
word
}
}
impl Register for u64 {
fn read(state: &State, s: u32) -> Self {
let s = s as usize;
u64::from(state.s[s + 1]) << 32 | u64::from(state.s[s])
}
fn write(self, state: &mut State, s: u32) {
let s = s as usize;
let [low, high] = halves(self);
state.s[s] = low;
state.s[s + 1] = high;
}
fn extend(word: u32, signed: bool) -> Self {
if signed {
i64::from(word.cast_signed()).cast_unsigned()
} else {
u64::from(word)
}
}
}
extern "C" fn execute_fp_check(core: &mut Family, execute: u32, pc: u32, index: u32) -> u64 {
if !core.fp_enabled(core.state.privileged()) {
core.raise_fault(Fault::NoCoprocessor);
return FAULTED;
}
if execute != 0
&& is_set(fpccr::LSPACT, core.system.fpccr)
&& !core.preserve_fp_state(Position::Instruction { pc, index })
{
return if core.state.locked_up != 0 {
FAULTED | KEEP_PC
} else {
FAULTED
};
}
if execute != 0
&& is_set(fpccr::ASPEN, core.system.fpccr)
&& !is_set(control::FPCA, core.state.control)
{
core.state.fpscr =
core.state.fpscr & !fp::FPSCR_DEFAULTS | core.system.fpdscr & fp::FPSCR_DEFAULTS;
core.state.control |= mask(control::FPCA);
}
0
}
extern "C" fn arithmetic<B: Register>(
core: &mut Family,
operation: u32,
d: u32,
n: u32,
m: u32,
) -> u32 {
let state = &mut core.state;
let (sd, sn, sm) = (B::read(state, d), B::read(state, n), B::read(state, m));
let operation = Arithmetic::ALL[operation as usize];
operation
.evaluate(sd, sn, sm, &mut state.fpscr)
.write(state, d);
0
}
extern "C" fn extremum<B: Register>(
core: &mut Family,
d: u32,
n: u32,
m: u32,
maximum: u32,
) -> u32 {
let state = &mut core.state;
let (sn, sm) = (B::read(state, n), B::read(state, m));
let result = if maximum != 0 {
fp::maximum_number(sn, sm, &mut state.fpscr)
} else {
fp::minimum_number(sn, sm, &mut state.fpscr)
};
result.write(state, d);
0
}
extern "C" fn compare<B: Register>(
core: &mut Family,
d: u32,
m: u32,
with_zero: u32,
quiet_nan_exc: u32,
) -> u32 {
let state = &mut core.state;
let op2 = if with_zero != 0 {
B::default()
} else {
B::read(state, m)
};
let flags = fp::compare(B::read(state, d), op2, quiet_nan_exc != 0, &mut state.fpscr);
state.fpscr = state.fpscr & !fp::FPSCR_NZCV | flags;
0
}
extern "C" fn to_fixed<S: Register, R: Register>(
core: &mut Family,
d: u32,
m: u32,
format: u32,
rounding: u32,
) -> u32 {
let state = &mut core.state;
let format = unpack(format);
let mode = Rounding::mode(rounding, state.fpscr);
let word = fp::to_fixed(S::read(state, m), format, mode, &mut state.fpscr);
R::extend(word, format.signed).write(state, d);
0
}
extern "C" fn from_fixed<R: Register>(
core: &mut Family,
d: u32,
m: u32,
format: u32,
rounding: u32,
) -> u32 {
let state = &mut core.state;
let mode = Rounding::mode(rounding, state.fpscr);
let operand = state.s[m as usize];
fp::from_fixed::<R>(operand, unpack(format), mode, &mut state.fpscr).write(state, d);
0
}
extern "C" fn to_half<S: Register>(core: &mut Family, d: u32, m: u32, top: u32) -> u32 {
let state = &mut core.state;
let half = fp::to_half(S::read(state, m), &mut state.fpscr);
let low = 16 * top;
let kept = state.s[d as usize] & !(0xFFFF << low);
state.s[d as usize] = kept | half << low;
0
}
extern "C" fn from_half<R: Register>(core: &mut Family, d: u32, m: u32, top: u32) -> u32 {
let state = &mut core.state;
let half = state.s[m as usize] >> (16 * top) & 0xFFFF;
fp::from_half::<R>(half, &mut state.fpscr).write(state, d);
0
}
extern "C" fn convert<A: Register, B: Register>(core: &mut Family, d: u32, m: u32) -> u32
where
A::Float: FloatConvert<B::Float>,
{
let state = &mut core.state;
fp::convert::<A, B>(A::read(state, m), &mut state.fpscr).write(state, d);
0
}
extern "C" fn round_to_integral<B: Register>(
core: &mut Family,
d: u32,
m: u32,
rounding: u32,
exact: u32,
) -> u32 {
let state = &mut core.state;
let mode = Rounding::mode(rounding, state.fpscr);
fp::round_to_integral(B::read(state, m), mode, exact != 0, &mut state.fpscr).write(state, d);
0
}
#[cfg(test)]
mod tests {
use super::super::super::registers::{cfsr, set};
use super::super::super::scs::SCS_BASE;
use super::super::testing::spec_of;
use super::super::testing::{Bench, DEVICE, RAM, emit_alone};
use super::*;
use crate::emulator::{Exit, Processor};
use crate::machine::armv7m::{Fpu, MpuRegions, Variant};
use crate::soc::Width;
use crate::time::VirtualNanos;
use cranelift_codegen::ir::Opcode;
const ONE: u32 = 0x3F80_0000;
const TWO: u32 = 0x4000_0000;
const THREE: u32 = 0x4040_0000;
const TEN: u32 = 0x4120_0000;
const QUIET_NAN: u32 = 0x7FC0_0000;
const CPACR_FULL: u32 = mask(cpacr::CP10) | mask(cpacr::CP11);
const ROUND_UP: u32 = set(fpscr::RMODE, 0, 0b01);
const ROUND_ZERO: u32 = set(fpscr::RMODE, 0, 0b11);
const UNORDERED: u32 = 0x3000_0000;
const EQUAL: u32 = 0x6000_0000;
const SINGLE_ARITHMETIC: [(&str, [u16; 2]); 14] = [
("vmla", [0xEE00, 0x0A81]),
("vmls", [0xEE00, 0x0AC1]),
("vnmla", [0xEE10, 0x0AC1]),
("vnmls", [0xEE10, 0x0A81]),
("vmul", [0xEE20, 0x0A81]),
("vnmul", [0xEE20, 0x0AC1]),
("vadd", [0xEE30, 0x0A81]),
("vsub", [0xEE30, 0x0AC1]),
("vdiv", [0xEE80, 0x0A81]),
("vfma", [0xEEA0, 0x0A81]),
("vfms", [0xEEA0, 0x0AC1]),
("vfnma", [0xEE90, 0x0AC1]),
("vfnms", [0xEE90, 0x0A81]),
("vsqrt", [0xEEB1, 0x0AE0]),
];
const EDGES: [u32; 28] = [
ONE,
ONE | fp::SIGN,
THREE,
0x3EAA_AAAB,
0xC2F6_E979,
0x3F7F_FFFF,
0x3F80_0001,
0x7F7F_FFFF,
0xFF7F_FFFF,
0x7F00_0000,
0x5F80_0000,
SMALLEST_NORMAL,
0x8080_0001,
0x0100_0000,
0x1F80_0000,
0x0080_0001,
0x0000_0000,
0x8000_0000,
0x7F80_0000,
0xFF80_0000,
QUIET_NAN,
0xFFC0_0001,
0x7F80_0001,
0xFFA0_0000,
0x0000_0001,
0x8000_0003,
0x0040_0000,
0x807F_FFFF,
];
fn bench(code: &[u16]) -> Bench {
let mut bench = Bench::new(code);
bench.core.system.cpacr = CPACR_FULL;
bench.core.state.control |= mask(control::FPCA);
bench
}
fn after(code: &[u16], setup: impl FnOnce(&mut Bench)) -> Bench {
let mut bench = bench(code);
setup(&mut bench);
let (_, ran) = bench.run(1);
assert_eq!(ran, 1, "the instruction retired");
assert_eq!(bench.core.state.r[15], 4, "and the next is after it");
bench
}
fn patterned(bench: &mut Bench) {
let bytes: Vec<u8> = (0..64)
.flat_map(|word| pattern(word).to_le_bytes())
.collect();
bench.write(RAM, &bytes);
}
fn pattern(word: u32) -> u32 {
0x1000_0000 + word
}
fn decoded(hw1: u16, hw2: u16) -> Decoded {
decoded_on(Fpu::FpV4Sp, hw1, hw2)
}
fn decoded_on(fpu: Fpu, hw1: u16, hw2: u16) -> Decoded {
decode(u32::from(hw1), u32::from(hw2), fpu)
}
fn transfer(
load: bool,
(d, n): (u32, u32),
offset: i32,
writeback: Option<i32>,
words: u32,
) -> Operation {
Operation::Memory(Transfer {
load,
d,
n,
offset,
writeback,
words,
double: false,
})
}
fn doubles(
load: bool,
(d, n): (u32, u32),
offset: i32,
writeback: Option<i32>,
words: u32,
) -> Operation {
let Operation::Memory(transfer) = transfer(load, (d, n), offset, writeback, words) else {
unreachable!("a transfer")
};
Operation::Memory(Transfer {
double: true,
..transfer
})
}
fn rounding(operation: Arithmetic, d: u32, n: u32, m: u32) -> Operation {
Operation::Arithmetic {
operation,
d,
n,
m,
precision: Precision::Single,
}
}
const fn fixed(size: u32, fraction_bits: u32, signed: bool) -> Fixed {
Fixed {
size,
fraction_bits,
signed,
}
}
#[test]
fn every_data_processing_encoding_decodes_to_its_instruction() {
use Arithmetic::*;
let precision = Precision::Single;
let moved = |sign| Operation::Move {
d: 0,
m: 1,
sign,
precision,
};
let constant = |d, value: u32| Operation::Constant {
d,
value: u64::from(value),
precision,
};
let compared = |m, quiet_nan_exc| Operation::Compare {
d: 0,
m,
quiet_nan_exc,
precision,
};
let cases = [
(
"vmla.f32 s0, s1, s2",
[0xEE00, 0x0A81],
rounding(MultiplyAccumulate, 0, 1, 2),
),
(
"vmls.f32 s0, s1, s2",
[0xEE00, 0x0AC1],
rounding(MultiplySubtract, 0, 1, 2),
),
(
"vnmla.f32 s0, s1, s2",
[0xEE10, 0x0AC1],
rounding(NegateMultiplyAccumulate, 0, 1, 2),
),
(
"vnmls.f32 s0, s1, s2",
[0xEE10, 0x0A81],
rounding(NegateMultiplySubtract, 0, 1, 2),
),
(
"vmul.f32 s0, s1, s2",
[0xEE20, 0x0A81],
rounding(Multiply, 0, 1, 2),
),
(
"vnmul.f32 s0, s1, s2",
[0xEE20, 0x0AC1],
rounding(NegateMultiply, 0, 1, 2),
),
(
"vadd.f32 s31, s17, s2",
[0xEE78, 0xFA81],
rounding(Add, 31, 17, 2),
),
(
"vsub.f32 s0, s1, s2",
[0xEE30, 0x0AC1],
rounding(Subtract, 0, 1, 2),
),
(
"vdiv.f32 s0, s1, s2",
[0xEE80, 0x0A81],
rounding(Divide, 0, 1, 2),
),
(
"vfma.f32 s0, s1, s2",
[0xEEA0, 0x0A81],
rounding(FusedMultiplyAccumulate, 0, 1, 2),
),
(
"vfms.f32 s0, s1, s2",
[0xEEA0, 0x0AC1],
rounding(FusedMultiplySubtract, 0, 1, 2),
),
(
"vfnma.f32 s0, s1, s2",
[0xEE90, 0x0AC1],
rounding(FusedNegateMultiplyAccumulate, 0, 1, 2),
),
(
"vfnms.f32 s0, s1, s2",
[0xEE90, 0x0A81],
rounding(FusedNegateMultiplySubtract, 0, 1, 2),
),
(
"vsqrt.f32 s0, s1",
[0xEEB1, 0x0AE0],
rounding(SquareRoot, 0, 1, 1),
),
("vabs.f32 s0, s1", [0xEEB0, 0x0AE0], moved(Sign::Clear)),
("vneg.f32 s0, s1", [0xEEB1, 0x0A60], moved(Sign::Flip)),
("vmov.f32 s0, s1", [0xEEB0, 0x0A60], moved(Sign::Keep)),
("vmov.f32 s0, #1.0", [0xEEB7, 0x0A00], constant(0, ONE)),
(
"vmov.f32 s5, #-0.125",
[0xEEFC, 0x2A00],
constant(5, 0xBE00_0000),
),
(
"vmov.f32 s5, #31.0",
[0xEEF3, 0x2A0F],
constant(5, 0x41F8_0000),
),
(
"vcmp.f32 s0, s1",
[0xEEB4, 0x0A60],
compared(Some(1), false),
),
(
"vcmpe.f32 s0, s1",
[0xEEB4, 0x0AE0],
compared(Some(1), true),
),
("vcmp.f32 s0, #0", [0xEEB5, 0x0A40], compared(None, false)),
("vcmpe.f32 s0, #0", [0xEEB5, 0x0AC0], compared(None, true)),
];
for (name, [hw1, hw2], expected) in cases {
assert_eq!(decoded(hw1, hw2), Decoded::Operation(expected), "{name}");
}
}
#[test]
fn every_conversion_encoding_decodes_to_its_form() {
let single = Precision::Single;
let to = |m, format, round_towards_zero| Operation::ToFixed {
d: 0,
m,
format,
rounding: if round_towards_zero {
Rounding::Fixed(Round::TowardZero)
} else {
Rounding::Fpscr
},
source: single,
result: single,
};
let from = |m, format, round_to_nearest| Operation::FromFixed {
d: 0,
m,
format,
rounding: if round_to_nearest {
Rounding::Fixed(Round::NearestTiesToEven)
} else {
Rounding::Fpscr
},
result: single,
};
#[rustfmt::skip]
let cases = [
("vcvt.s32.f32 s0, s1", [0xEEBD, 0x0AE0], to(1, integer(true), true)),
("vcvtr.s32.f32 s0, s1", [0xEEBD, 0x0A60], to(1, integer(true), false)),
("vcvt.u32.f32 s0, s1", [0xEEBC, 0x0AE0], to(1, integer(false), true)),
("vcvtr.u32.f32 s0, s1", [0xEEBC, 0x0A60], to(1, integer(false), false)),
("vcvt.f32.s32 s0, s1", [0xEEB8, 0x0AE0], from(1, integer(true), false)),
("vcvt.f32.u32 s0, s1", [0xEEB8, 0x0A60], from(1, integer(false), false)),
("vcvt.s16.f32 s0, s0, #8", [0xEEBE, 0x0A44], to(0, fixed(16, 8, true), true)),
("vcvt.u16.f32 s0, s0, #16", [0xEEBF, 0x0A40], to(0, fixed(16, 16, false), true)),
("vcvt.s32.f32 s0, s0, #1", [0xEEBE, 0x0AEF], to(0, fixed(32, 1, true), true)),
("vcvt.u32.f32 s0, s0, #32", [0xEEBF, 0x0AC0], to(0, fixed(32, 32, false), true)),
("vcvt.f32.s16 s0, s0, #8", [0xEEBA, 0x0A44], from(0, fixed(16, 8, true), true)),
("vcvt.f32.u16 s0, s0, #0", [0xEEBB, 0x0A48], from(0, fixed(16, 0, false), true)),
("vcvt.f32.s32 s0, s0, #16", [0xEEBA, 0x0AC8], from(0, fixed(32, 16, true), true)),
("vcvt.f32.u32 s0, s0, #32", [0xEEBB, 0x0AC0], from(0, fixed(32, 32, false), true)),
("vcvtb.f32.f16 s0, s1", [0xEEB2, 0x0A60], Operation::FromHalf { d: 0, m: 1, top: false, result: single }),
("vcvtt.f32.f16 s0, s1", [0xEEB2, 0x0AE0], Operation::FromHalf { d: 0, m: 1, top: true, result: single }),
("vcvtb.f16.f32 s0, s1", [0xEEB3, 0x0A60], Operation::ToHalf { d: 0, m: 1, top: false, source: single }),
("vcvtt.f16.f32 s0, s1", [0xEEB3, 0x0AE0], Operation::ToHalf { d: 0, m: 1, top: true, source: single }),
];
for (name, [hw1, hw2], expected) in cases {
assert_eq!(decoded(hw1, hw2), Decoded::Operation(expected), "{name}");
}
}
#[test]
fn every_transfer_encoding_decodes_to_its_form() {
let core = |to_core, t, s| Operation::Core { to_core, t, s };
let pair = |to_core, s| Operation::CorePair {
to_core,
t: 1,
t2: 2,
s,
};
#[rustfmt::skip]
let cases = [
("vmov s3, r2", [0xEE01, 0x2A90], core(false, 2, 3)),
("vmov r2, s3", [0xEE11, 0x2A90], core(true, 2, 3)),
("vmov s30, s31, r1, r2", [0xEC42, 0x1A1F], pair(false, 30)),
("vmov r1, r2, s30, s31", [0xEC52, 0x1A1F], pair(true, 30)),
("vmov d15, r1, r2", [0xEC42, 0x1B1F], pair(false, 30)),
("vmov r1, r2, d15", [0xEC52, 0x1B1F], pair(true, 30)),
("vmov.32 d3[1], r4", [0xEE23, 0x4B10], core(false, 4, 7)),
("vmov.32 r4, d3[1]", [0xEE33, 0x4B10], core(true, 4, 7)),
("vmov.32 d15[0], r4", [0xEE0F, 0x4B10], core(false, 4, 30)),
("vmrs r3, fpscr", [0xEEF1, 0x3A10], Operation::ReadStatus { t: 3 }),
("vmrs APSR_nzcv, fpscr", [0xEEF1, 0xFA10], Operation::ReadStatus { t: 15 }),
("vmsr fpscr, r3", [0xEEE1, 0x3A10], Operation::WriteStatus { t: 3 }),
("vldr s4, [r0, #8]", [0xED90, 0x2A02], transfer(true, (4, 0), 8, None, 1)),
("vldr s4, [r0, #-8]", [0xED10, 0x2A02], transfer(true, (4, 0), -8, None, 1)),
("vldr d2, [r0, #8]", [0xED90, 0x2B02], doubles(true, (4, 0), 8, None, 2)),
("vldr s4, [pc, #8]", [0xED9F, 0x2A02], transfer(true, (4, 15), 8, None, 1)),
("vldr d2, [pc, #-4]", [0xED1F, 0x2B01], doubles(true, (4, 15), -4, None, 2)),
("vstr s4, [r0, #8]", [0xED80, 0x2A02], transfer(false, (4, 0), 8, None, 1)),
("vstr d2, [r0, #-8]", [0xED00, 0x2B02], doubles(false, (4, 0), -8, None, 2)),
("vldmia r0, {s2-s5}", [0xEC90, 0x1A04], transfer(true, (2, 0), 0, None, 4)),
("vldmia r0!, {s2-s5}", [0xECB0, 0x1A04], transfer(true, (2, 0), 0, Some(16), 4)),
("vldmdb r0!, {s2-s5}", [0xED30, 0x1A04], transfer(true, (2, 0), -16, Some(-16), 4)),
("vldmia r0, {d1-d2}", [0xEC90, 0x1B04], doubles(true, (2, 0), 0, None, 4)),
("vldmdb r0!, {d1-d2}", [0xED30, 0x1B04], doubles(true, (2, 0), -16, Some(-16), 4)),
("vstmia r0, {s2-s5}", [0xEC80, 0x1A04], transfer(false, (2, 0), 0, None, 4)),
("vstmia r0!, {s2-s5}", [0xECA0, 0x1A04], transfer(false, (2, 0), 0, Some(16), 4)),
("vstmdb r0!, {s2-s5}", [0xED20, 0x1A04], transfer(false, (2, 0), -16, Some(-16), 4)),
("vstmia r0, {d1-d2}", [0xEC80, 0x1B04], doubles(false, (2, 0), 0, None, 4)),
("vpush {s16-s17}", [0xED2D, 0x8A02], transfer(false, (16, 13), -8, Some(-8), 2)),
("vpush {d8}", [0xED2D, 0x8B02], doubles(false, (16, 13), -8, Some(-8), 2)),
("vpop {s16-s17}", [0xECBD, 0x8A02], transfer(true, (16, 13), 0, Some(8), 2)),
("vpop {d8}", [0xECBD, 0x8B02], doubles(true, (16, 13), 0, Some(8), 2)),
("vldmia r1, {s0-s31}", [0xEC91, 0x0A20], transfer(true, (0, 1), 0, None, 32)),
("vstmia r1, {d0-d15}", [0xEC81, 0x0B20], doubles(false, (0, 1), 0, None, 32)),
("fldmiax r0!, {d1-d2}", [0xECB0, 0x1B05], doubles(true, (2, 0), 0, Some(20), 4)),
("fstmdbx r0!, {d1-d2}", [0xED20, 0x1B05], doubles(false, (2, 0), -20, Some(-20), 4)),
];
for (name, [hw1, hw2], expected) in cases {
assert_eq!(decoded(hw1, hw2), Decoded::Operation(expected), "{name}");
}
}
#[test]
fn undefined_and_unpredictable_encodings_are_undefined() {
let cases = [
("vadd.f64, sz set", [0xEE31, 0x0B02]),
("vsel, T set", [0xFE00, 0x0A00]),
("vmaxnm, T set", [0xFE80, 0x0A00]),
("vcvta, T set", [0xFEBC, 0x0A40]),
("mcr2 to CP10, T set", [0xFE01, 0x2A90]),
("vdiv with op set", [0xEE80, 0x0AC1]),
("vrintr, FPv5", [0xEEB6, 0x0A60]),
("vcvt.f64.f32, FPv5", [0xEEB7, 0x0AC0]),
("opc2 1001", [0xEEB9, 0x0A40]),
("vcvt.f32.s16 with 17 fraction bits", [0xEEBA, 0x0A68]),
("vldm with P, U and W set", [0xEDB0, 0x1A04]),
("vldm from the PC", [0xEC9F, 0x1A04]),
("vldm of no registers", [0xEC90, 0x1A00]),
("vldm past s31", [0xEC90, 0xFA03]),
("vldm.64 of 17 registers", [0xEC90, 0x0B22]),
("vldm.64 past d15", [0xEC90, 0xFB04]),
("vldm.64 from d16", [0xECD0, 0x0B02]),
("vstr to the PC", [0xED8F, 0x2A02]),
("vstr d16 to the PC", [0xEDCF, 0x0B00]),
("vmov s3, sp", [0xEE01, 0xDA90]),
("vmov s3, pc", [0xEE01, 0xFA90]),
("vmov r1, r1, s30, s31", [0xEC51, 0x1A1F]),
("vmov s31, s32, r1, r2", [0xEC42, 0x1A3F]),
("vmov s30, s31, sp, r2", [0xEC42, 0xDA1F]),
("vmov d31, sp, r2", [0xEC42, 0xDB3F]),
("vmov.32 d16[0], sp", [0xEE00, 0xDB90]),
("vmov scalar with B set", [0xEE00, 0x4B30]),
("32-bit transfer with A 010", [0xEE40, 0x2A10]),
("vmrs sp, fpscr", [0xEEF1, 0xDA10]),
("vmsr fpscr, pc", [0xEEE1, 0xFA10]),
("vmsr fpscr, sp", [0xEEE1, 0xDA10]),
("64-bit transfer with op 01x1", [0xEC42, 0x1A5F]),
("64-bit transfer with op 00x0", [0xEC42, 0x1A0F]),
];
for (name, [hw1, hw2]) in cases {
assert_eq!(decoded(hw1, hw2), Decoded::Undefined, "{name}");
}
}
#[test]
fn a_wrong_should_be_bit_is_undefined() {
let cases = [
("vmrs from special register 0000", [0xEEF0, 0x3A10]),
("vmsr to special register 1000", [0xEEE8, 0x3A10]),
("vmrs with bit 7 set", [0xEEF1, 0x3A90]),
("vmrs with bit 0 set", [0xEEF1, 0x3A11]),
("vmov s3, r2 with bit 5 set", [0xEE01, 0x2AB0]),
("vmov s3, r2 with bit 0 set", [0xEE01, 0x2A91]),
("vmov.32 d3[1], r4 with bit 0 set", [0xEE23, 0x4B11]),
("vmov.f32 s0, #1.0 with bit 7 set", [0xEEB7, 0x0A80]),
("vmov.f32 s0, #1.0 with bit 5 set", [0xEEB7, 0x0A20]),
("vcmp.f32 s0, #0 with bit 5 set", [0xEEB5, 0x0A60]),
("vcmp.f32 s0, #0 with bit 0 set", [0xEEB5, 0x0A41]),
];
for (name, [hw1, hw2]) in cases {
assert_eq!(decoded(hw1, hw2), Decoded::Undefined, "{name}");
}
}
#[test]
fn a_transfer_naming_d16_up_checks_the_context_before_it_is_undefined() {
let cases = [
("vldr d16, [r0, #8]", [0xEDD0, 0x0B02]),
("vstr d16, [r0]", [0xEDC0, 0x0B00]),
("vmov d16, r1, r2", [0xEC42, 0x1B30]),
("vmov r1, r2, d31", [0xEC52, 0x1B3F]),
("vmov.32 d16[0], r4", [0xEE00, 0x4B90]),
("vmov.32 r4, d16[1]", [0xEE30, 0x4B90]),
];
for (name, [hw1, hw2]) in cases {
assert_eq!(
decoded(hw1, hw2),
Decoded::Operation(Operation::BeyondBank),
"{name}"
);
}
assert_eq!(
decoded(0xECD0, 0x0B02),
Decoded::Undefined,
"vldm.64 from d16"
);
}
#[test]
fn a_vldm_or_vstm_that_faults_part_way_changes_no_register() {
for (name, code, words) in [
("vldmia r0!, {s2-s5}", [0xECB0, 0x1A04], 3),
("vstmia r0!, {s2-s5}", [0xECA0, 0x1A04], 3),
] {
let mut bench = bench(&code);
bench.bus.fails_from = Some(u64::from(DEVICE + 8));
bench.bus.answer = 0x5A5A_5A5A;
bench.core.state.r[0] = DEVICE;
bench.core.state.s[2..6].copy_from_slice(&[ONE, TWO, THREE, TEN]);
let (exit, ran) = bench.run(1);
assert_eq!((exit, ran), (Exit::Returned, 0), "{name}");
let state = &bench.core.state;
assert_eq!(state.s[2..6], [ONE, TWO, THREE, TEN], "{name}'s registers");
assert_eq!(state.r[0], DEVICE, "{name}'s base");
assert_eq!(state.r[15], 0, "{name} runs again");
assert_eq!(bench.bus.accesses.len(), words, "{name} stops at the fault");
}
}
#[test]
fn other_coprocessors_are_absent_and_unallocated_space_is_undefined() {
for (name, [hw1, hw2]) in [
("cdp p0", [0xEE00, 0x0000]),
("ldc p7", [0xED90, 0x7000]),
("mrrc p15", [0xEC52, 0x1F00]),
("mcr2 p14", [0xFE01, 0x2E10]),
("cdp p9", [0xEE00, 0x0900]),
("cdp p12", [0xEE00, 0x0C00]),
] {
assert_eq!(decoded(hw1, hw2), Decoded::Absent, "{name}");
}
for (name, [hw1, hw2]) in [
("op1 000000", [0xEC00, 0x0A00]),
("op1 000001", [0xEC10, 0x0A00]),
("op1 110000", [0xEF00, 0x0A00]),
] {
assert_eq!(decoded(hw1, hw2), Decoded::Unallocated, "{name}");
}
}
#[test]
fn vfp_expand_imm_gives_table_a6_6() {
for (name, imm8, single, double) in [
("1", 0x70, ONE, 0x3FF0_0000_0000_0000),
("2", 0x00, TWO, 0x4000_0000_0000_0000),
("0.125", 0x40, 0x3E00_0000, 0x3FC0_0000_0000_0000),
("-0.125", 0xC0, 0xBE00_0000, 0xBFC0_0000_0000_0000),
("1.9375", 0x7F, 0x3FF8_0000, 0x3FFF_0000_0000_0000),
("31", 0x3F, 0x41F8_0000, 0x403F_0000_0000_0000),
] {
assert_eq!(
expand_imm(imm8, Precision::Single),
u64::from(single),
"{name}"
);
assert_eq!(expand_imm(imm8, Precision::Double), double, "{name}");
}
}
#[test]
fn every_arithmetic_instruction_computes_its_pseudocode() {
let cases = [
("vmla", [0xEE00, 0x0A81], 0x4180_0000, 0),
("vmls", [0xEE00, 0x0AC1], 0x4080_0000, 0),
("vnmla", [0xEE10, 0x0AC1], 0xC180_0000, 0),
("vnmls", [0xEE10, 0x0A81], 0xC080_0000, 0),
("vmul", [0xEE20, 0x0A81], 0x40C0_0000, 0),
("vnmul", [0xEE20, 0x0AC1], 0xC0C0_0000, 0),
("vsub", [0xEE30, 0x0AC1], 0xBF80_0000, 0),
("vdiv", [0xEE80, 0x0A81], 0x3F2A_AAAB, mask(fpscr::IXC)),
("vfma", [0xEEA0, 0x0A81], 0x4180_0000, 0),
("vfms", [0xEEA0, 0x0AC1], 0x4080_0000, 0),
("vfnma", [0xEE90, 0x0AC1], 0xC180_0000, 0),
("vfnms", [0xEE90, 0x0A81], 0xC080_0000, 0),
(
"vsqrt s0, s1",
[0xEEB1, 0x0AE0],
0x3FB5_04F3,
mask(fpscr::IXC),
),
];
for (name, code, expected, flags) in cases {
let bench = after(&code, |b| {
b.core.state.s[..3].copy_from_slice(&[TEN, TWO, THREE]);
});
assert_eq!(bench.core.state.s[0], expected, "{name}");
assert_eq!(bench.core.state.fpscr, flags, "{name}'s exceptions");
}
let bench = after(&[0xEE78, 0xFA81], |b| {
b.core.state.s[17] = ONE;
b.core.state.s[2] = TWO;
});
assert_eq!(bench.core.state.s[31], THREE, "vadd s31, s17, s2");
}
#[test]
fn vmla_rounds_twice_and_vfma_once() {
let operands = [0xBF80_0000, 0x3F80_0001, 0x3F7F_FFFE];
let setup = |b: &mut Bench| b.core.state.s[..3].copy_from_slice(&operands);
let chained = after(&[0xEE00, 0x0A81], setup);
assert_eq!(
(chained.core.state.s[0], chained.core.state.fpscr),
(0, mask(fpscr::IXC))
);
let fused = after(&[0xEEA0, 0x0A81], setup);
assert_eq!(
(fused.core.state.s[0], fused.core.state.fpscr),
(0xA880_0000, 0),
"-2^-46"
);
}
#[test]
fn vnmla_of_opposites_is_plus_zero_with_ixc_set() {
let bench = after(&[0xEE10, 0x0AC1], |b| {
b.core.state.s[..3].copy_from_slice(&[ONE, ONE, ONE | fp::SIGN]);
b.core.state.fpscr = mask(fpscr::IXC);
});
assert_eq!(
(bench.core.state.s[0], bench.core.state.fpscr),
(0, mask(fpscr::IXC))
);
}
#[test]
fn the_smallest_normal_underflows_only_from_below() {
let below_one = 0x3F7F_FFFF;
for (s1, flags) in [
(SMALLEST_NORMAL + 1, mask(fpscr::IXC)),
(SMALLEST_NORMAL, mask(fpscr::IXC) | mask(fpscr::UFC)),
] {
let bench = after(&[0xEE20, 0x0A81], |b| {
b.core.state.s[1] = s1;
b.core.state.s[2] = below_one;
b.core.state.fpscr = mask(fpscr::IXC);
});
assert_eq!(
(bench.core.state.s[0], bench.core.state.fpscr),
(SMALLEST_NORMAL, flags),
"vmul.f32 of {s1:#010x}"
);
}
}
#[test]
fn the_host_answers_what_fp_does_with_ixc_set() {
const DRAWS: u64 = 2000;
const SEED: u64 = 0x5EED;
let mut covered = Vec::new();
for (name, code) in SINGLE_ARITHMETIC {
let Decoded::Operation(Operation::Arithmetic {
operation, d, n, m, ..
}) = decoded(code[0], code[1])
else {
panic!("{name} rounds");
};
covered.push(operation);
let mut bench = bench(&code);
for index in 0..DRAWS {
let operand = |which: u64| {
let drawn = crate::edge::draw(SEED, name, 3 * index + which);
let high = u32::try_from(drawn >> 32).expect("the high half");
if drawn.is_multiple_of(4) {
high
} else {
EDGES[high as usize % EDGES.len()]
}
};
let operands = [operand(0), operand(1), operand(2)];
bench.core.state.s[..3].copy_from_slice(&operands);
bench.core.state.fpscr = mask(fpscr::IXC);
bench.core.state.r[15] = 0;
assert_eq!(bench.run(1).1, 1, "{name} retired");
let mut fpscr = mask(fpscr::IXC);
let [sd, sn, sm] = [d, n, m].map(|register| operands[register as usize]);
let expected = operation.evaluate(sd, sn, sm, &mut fpscr);
assert_eq!(
(bench.core.state.s[d as usize], bench.core.state.fpscr),
(expected, fpscr),
"{name} of {operands:#010x?}"
);
}
}
assert_eq!(
covered,
Arithmetic::ALL,
"every instruction the host can run"
);
}
#[test]
fn a_fused_instruction_runs_on_the_host_only_with_its_instruction() {
let lowers_fma = |fma: Fma| {
let ((), function) = emit_alone(|e| {
e.fma = fma;
e.begin(0, 0, 4, 0);
let vfma = rounding(Arithmetic::FusedMultiplyAccumulate, 0, 1, 2);
assert!(emit(e, vfma).is_ok(), "vfma lowers");
});
function
.layout
.blocks()
.flat_map(|block| function.layout.block_insts(block))
.any(|inst| function.dfg.insts[inst].opcode() == Opcode::Fma)
};
assert_eq!(
(lowers_fma(Fma::Library), lowers_fma(Fma::Instruction)),
(false, true)
);
}
#[test]
fn the_moves_change_only_the_sign() {
let signaling = 0xFF80_0001;
for (name, code, operand, expected) in [
("vmov.f32 s0, s1", [0xEEB0, 0x0A60], signaling, signaling),
("vabs.f32 s0, s1", [0xEEB0, 0x0AE0], signaling, 0x7F80_0001),
(
"vneg.f32 s0, s1",
[0xEEB1, 0x0A60],
0x0000_0001,
0x8000_0001,
),
] {
let bench = after(&code, |b| {
b.core.state.s[1] = operand;
b.core.state.fpscr = mask(fpscr::FZ);
});
assert_eq!(bench.core.state.s[0], expected, "{name}");
assert_eq!(
bench.core.state.fpscr,
mask(fpscr::FZ),
"{name} raises nothing"
);
}
}
#[test]
fn vmov_immediate_writes_its_constant() {
for (code, d, expected) in [
([0xEEB7, 0x0A00], 0, ONE),
([0xEEFC, 0x2A00], 5, 0xBE00_0000),
([0xEEF3, 0x2A0F], 5, 0x41F8_0000),
] {
let bench = after(&code, |_| {});
assert_eq!(bench.core.state.s[d], expected);
}
}
#[test]
fn every_compare_sets_fpscr_flags() {
let cases = [
("vcmp s0, s1", [0xEEB4, 0x0A60], [ONE, TWO], mask(fpscr::N)),
(
"vcmp s0, s1, unordered",
[0xEEB4, 0x0A60],
[QUIET_NAN, TWO],
UNORDERED,
),
(
"vcmpe s0, s1",
[0xEEB4, 0x0AE0],
[QUIET_NAN, ONE],
UNORDERED | mask(fpscr::IOC),
),
("vcmpe s0, s1, equal", [0xEEB4, 0x0AE0], [TWO, TWO], EQUAL),
(
"vcmp s0, #0",
[0xEEB5, 0x0A40],
[0x8000_0000, QUIET_NAN],
EQUAL,
),
(
"vcmp s0, #0, greater",
[0xEEB5, 0x0A40],
[ONE, QUIET_NAN],
mask(fpscr::C),
),
(
"vcmpe s0, #0",
[0xEEB5, 0x0AC0],
[QUIET_NAN, ONE],
UNORDERED | mask(fpscr::IOC),
),
];
for (name, code, operands, expected) in cases {
let bench = after(&code, |b| {
b.core.state.s[..2].copy_from_slice(&operands);
b.core.state.fpscr = fp::FPSCR_NZCV;
});
assert_eq!(bench.core.state.fpscr, expected, "{name}");
}
}
#[test]
fn vmrs_and_vmsr_move_fpscr() {
let bench = after(&[0xEEF1, 0x3A10], |b| b.core.state.fpscr = 0x1234_5678);
assert_eq!(bench.core.state.r[3], 0x1234_5678, "vmrs r3, fpscr");
let bench = after(&[0xEEF1, 0xFA10], |b| b.core.state.fpscr = EQUAL);
let state = &bench.core.state;
assert_eq!(
(state.n, state.z, state.c, state.v),
(0, 1, 1, 0),
"vmrs APSR_nzcv, fpscr"
);
let bench = after(&[0xEEE1, 0x3A10], |b| b.core.state.r[3] = u32::MAX);
assert_eq!(
bench.core.state.fpscr,
fp::FPSCR_WRITABLE,
"vmsr keeps out the reserved bits"
);
}
#[test]
fn every_conversion_converts_with_its_rounding() {
#[rustfmt::skip]
let cases = [
("vcvt.s32.f32 s0, s1", [0xEEBD, 0x0AE0], (1, 0x4020_0000), ROUND_UP, (2, mask(fpscr::IXC))),
("vcvtr.s32.f32 s0, s1", [0xEEBD, 0x0A60], (1, 0x4020_0000), ROUND_UP, (3, mask(fpscr::IXC))),
("vcvt.u32.f32 s0, s1", [0xEEBC, 0x0AE0], (1, 0xBF80_0000), ROUND_UP, (0, mask(fpscr::IOC))),
("vcvtr.u32.f32 s0, s1", [0xEEBC, 0x0A60], (1, 0x4020_0000), ROUND_UP, (3, mask(fpscr::IXC))),
("vcvt.f32.s32 s0, s1", [0xEEB8, 0x0AE0], (1, u32::MAX), ROUND_ZERO, (0xBF80_0000, 0)),
("vcvt.f32.u32 s0, s1", [0xEEB8, 0x0A60], (1, u32::MAX), ROUND_ZERO, (0x4F7F_FFFF, mask(fpscr::IXC))),
("vcvt.s16.f32 s0, s0, #8", [0xEEBE, 0x0A44], (0, 0xBFC0_0000), 0, (0xFFFF_FE80, 0)),
("vcvt.u16.f32 s0, s0, #16", [0xEEBF, 0x0A40], (0, 0x3F00_0000), 0, (0x8000, 0)),
("vcvt.s32.f32 s0, s0, #1", [0xEEBE, 0x0AEF], (0, 0x3FE0_0000), ROUND_UP, (3, mask(fpscr::IXC))),
("vcvt.u32.f32 s0, s0, #32", [0xEEBF, 0x0AC0], (0, 0x3E80_0000), 0, (0x4000_0000, 0)),
("vcvt.f32.s16 s0, s0, #8", [0xEEBA, 0x0A44], (0, 0x1234_FE80), 0, (0xBFC0_0000, 0)),
("vcvt.f32.u16 s0, s0, #0", [0xEEBB, 0x0A48], (0, 0xABCD_8000), 0, (0x4700_0000, 0)),
("vcvt.f32.s32 s0, s0, #16", [0xEEBA, 0x0AC8], (0, 0xFFFF_0000), 0, (0xBF80_0000, 0)),
("vcvt.f32.u32 s0, s0, #32", [0xEEBB, 0x0AC0], (0, u32::MAX), ROUND_ZERO, (ONE, mask(fpscr::IXC))),
];
for (name, code, (source, operand), mode, (expected, flags)) in cases {
let bench = after(&code, |b| {
b.core.state.s[source] = operand;
b.core.state.fpscr = mode;
});
assert_eq!(bench.core.state.s[0], expected, "{name}");
assert_eq!(bench.core.state.fpscr, mode | flags, "{name}'s exceptions");
}
}
#[test]
fn vcvtb_and_vcvtt_use_the_half_they_name() {
for (name, code, operand, expected) in [
("vcvtb.f32.f16 s0, s1", [0xEEB2, 0x0A60], 0xC000_3C00, ONE),
(
"vcvtt.f32.f16 s0, s1",
[0xEEB2, 0x0AE0],
0xC000_3C00,
0xC000_0000,
),
("vcvtb.f16.f32 s0, s1", [0xEEB3, 0x0A60], ONE, 0xAAAA_3C00),
("vcvtt.f16.f32 s0, s1", [0xEEB3, 0x0AE0], ONE, 0x3C00_5555),
] {
let bench = after(&code, |b| {
b.core.state.s[..2].copy_from_slice(&[0xAAAA_5555, operand]);
});
assert_eq!(bench.core.state.s[0], expected, "{name}");
}
}
#[test]
fn every_vmov_transfer_moves_its_words() {
let bench = after(&[0xEE01, 0x2A90], |b| b.core.state.r[2] = 0xDEAD_BEEF);
assert_eq!(bench.core.state.s[3], 0xDEAD_BEEF, "vmov s3, r2");
let bench = after(&[0xEE11, 0x2A90], |b| b.core.state.s[3] = 0x1234);
assert_eq!(bench.core.state.r[2], 0x1234, "vmov r2, s3");
for (name, code) in [
("vmov s30, s31, r1, r2", [0xEC42, 0x1A1F]),
("vmov d15, r1, r2", [0xEC42, 0x1B1F]),
] {
let bench = after(&code, |b| b.core.state.r[1..3].copy_from_slice(&[0xA, 0xB]));
assert_eq!(bench.core.state.s[30..], [0xA, 0xB], "{name}");
}
for (name, code) in [
("vmov r1, r2, s30, s31", [0xEC52, 0x1A1F]),
("vmov r1, r2, d15", [0xEC52, 0x1B1F]),
] {
let bench = after(&code, |b| b.core.state.s[30..].copy_from_slice(&[0xA, 0xB]));
assert_eq!(bench.core.state.r[1..3], [0xA, 0xB], "{name}");
}
let bench = after(&[0xEE23, 0x4B10], |b| b.core.state.r[4] = 0x77);
assert_eq!(bench.core.state.s[7], 0x77, "vmov.32 d3[1], r4");
let bench = after(&[0xEE33, 0x4B10], |b| b.core.state.s[7] = 0x77);
assert_eq!(bench.core.state.r[4], 0x77, "vmov.32 r4, d3[1]");
let bench = after(&[0xEE0F, 0x4B10], |b| b.core.state.r[4] = 0x30);
assert_eq!(bench.core.state.s[30], 0x30, "vmov.32 d15[0], r4");
}
#[test]
fn vldr_loads_from_every_base() {
let cases = [
("vldr s4, [r0, #8]", [0xED90, 0x2A02], RAM, 1),
("vldr s4, [r0, #-8]", [0xED10, 0x2A02], RAM + 16, 1),
("vldr d2, [r0, #8]", [0xED90, 0x2B02], RAM, 2),
];
for (name, code, base, words) in cases {
let bench = after(&code, |b| {
patterned(b);
b.core.state.r[0] = base;
});
let expected = [pattern(2), pattern(3)];
assert_eq!(
bench.core.state.s[4..4 + words],
expected[..words],
"{name}"
);
}
for (name, code, data, words) in [
("vldr s4, [pc, #8]", [0xED9F, 0x2A02], 0x10C, 1),
("vldr d2, [pc, #-16]", [0xED1F, 0x2B04], 0xF4, 2),
] {
let mut bench = bench(&[]);
bench.place(0x102, &code);
bench.write(data, &[1, 0, 0, 0, 2, 0, 0, 0]);
bench.core.state.r[15] = 0x102;
bench.run(1);
let expected = [1, 2];
assert_eq!(
bench.core.state.s[4..4 + words],
expected[..words],
"{name} from Align(PC, 4)"
);
}
}
#[test]
fn a_big_endian_part_keeps_a_doubles_high_word_first() {
let mut bench = Bench::big_endian(&[
0xED90, 0x0B00, 0xED81, 0x0B00, 0xED90, 0x2A00, ]);
bench.core.system.cpacr = CPACR_FULL;
bench.core.state.control |= mask(control::FPCA);
bench.write(RAM, &[0x3F, 0xF0, 0, 0, 0, 0, 0, 0]);
bench.core.state.r[0] = RAM;
bench.core.state.r[1] = RAM + 16;
assert_eq!(bench.run(3).1, 3);
let s = &bench.core.state.s;
assert_eq!(u64::from(s[1]) << 32 | u64::from(s[0]), 1.0_f64.to_bits());
assert_eq!(s[4], 0x3FF0_0000);
assert_eq!(
(bench.word(RAM + 16), bench.word(RAM + 20)),
(0x3FF0_0000, 0)
);
}
#[test]
fn vldm_and_vpop_load_every_form() {
let cases = [
("vldmia r0, {s2-s5}", [0xEC90, 0x1A04], 0, RAM + 8, RAM + 8),
(
"vldmia r0!, {s2-s5}",
[0xECB0, 0x1A04],
0,
RAM + 8,
RAM + 24,
),
(
"vldmdb r0!, {s2-s5}",
[0xED30, 0x1A04],
0,
RAM + 24,
RAM + 8,
),
("vldmia r0, {d1-d2}", [0xEC90, 0x1B04], 0, RAM + 8, RAM + 8),
(
"vldmdb r0!, {d1-d2}",
[0xED30, 0x1B04],
0,
RAM + 24,
RAM + 8,
),
(
"fldmiax r0!, {d1-d2}",
[0xECB0, 0x1B05],
0,
RAM + 8,
RAM + 28,
),
("vpop {s2-s5}", [0xECBD, 0x1A04], 13, RAM + 8, RAM + 24),
("vpop {d1-d2}", [0xECBD, 0x1B04], 13, RAM + 8, RAM + 24),
];
for (name, code, n, base, written) in cases {
let bench = after(&code, |b| {
patterned(b);
b.core.state.r[n] = base;
});
let expected = [pattern(2), pattern(3), pattern(4), pattern(5)];
assert_eq!(bench.core.state.s[2..6], expected, "{name}");
assert_eq!(bench.core.state.r[n], written, "{name}'s base");
}
let bench = after(&[0xEC91, 0x0A20], |b| {
patterned(b);
b.core.state.r[1] = RAM;
});
let expected: Vec<u32> = (0..32).map(pattern).collect();
assert_eq!(bench.core.state.s[..], expected, "vldmia r1, {{s0-s31}}");
}
#[test]
fn vstr_vstm_and_vpush_store_every_form() {
let cases = [
(
"vstr s4, [r0, #8]",
[0xED80, 0x2A02],
(0, RAM),
(4, RAM + 8, 1),
RAM,
),
(
"vstr d2, [r0, #-8]",
[0xED00, 0x2B02],
(0, RAM + 16),
(4, RAM + 8, 2),
RAM + 16,
),
(
"vstmia r0, {s2-s5}",
[0xEC80, 0x1A04],
(0, RAM),
(2, RAM, 4),
RAM,
),
(
"vstmia r0!, {s2-s5}",
[0xECA0, 0x1A04],
(0, RAM),
(2, RAM, 4),
RAM + 16,
),
(
"vstmdb r0!, {s2-s5}",
[0xED20, 0x1A04],
(0, RAM + 16),
(2, RAM, 4),
RAM,
),
(
"vstmia r0, {d1-d2}",
[0xEC80, 0x1B04],
(0, RAM),
(2, RAM, 4),
RAM,
),
(
"fstmdbx r0!, {d1-d2}",
[0xED20, 0x1B05],
(0, RAM + 20),
(2, RAM, 4),
RAM,
),
(
"vpush {s16-s17}",
[0xED2D, 0x8A02],
(13, RAM + 16),
(16, RAM + 8, 2),
RAM + 8,
),
(
"vpush {d8}",
[0xED2D, 0x8B02],
(13, RAM + 16),
(16, RAM + 8, 2),
RAM + 8,
),
];
let numbered = |register: usize| 0xA0 + u32::try_from(register).expect("one of 32");
for (name, code, (n, base), (d, first, words), written) in cases {
let bench = after(&code, |b| {
b.write(RAM, &[0xEE; 32]);
b.core.state.r[n] = base;
b.core.state.s = std::array::from_fn(numbered);
});
for word in 0..words {
let stored = bench.word(first + 4 * word);
assert_eq!(stored, numbered(d) + word, "{name}'s word {word}");
}
assert_eq!(
bench.word(first + 4 * words),
0xEEEE_EEEE,
"{name} stores no more"
);
assert_eq!(bench.core.state.r[n], written, "{name}'s base");
}
let bench = after(&[0xEC81, 0x0B20], |b| {
b.core.state.r[1] = RAM;
b.core.state.s = std::array::from_fn(numbered);
});
for word in 0..32 {
assert_eq!(
bench.word(RAM + 4 * word),
0xA0 + word,
"vstmia r1, {{d0-d15}}"
);
}
}
#[test]
fn a_core_without_the_extension_has_no_coprocessor() {
for (fpu, code, status) in [
(Fpu::None, [0xEE80, 0x0A81], mask(cfsr::NOCP)),
(Fpu::None, [0xEC00, 0x0A00], mask(cfsr::UNDEFINSTR)),
(Fpu::FpV4Sp, [0xEE80, 0x0A81], 0),
] {
let mut bench = Bench::of(&spec_of(Variant::M4, fpu, MpuRegions::Eight), &code);
let cpacr = SCS_BASE + 0xD88;
bench.core.debugger_access(
Some(u64::from(CPACR_FULL)),
cpacr,
Width::W32,
VirtualNanos(0),
);
bench.run(1);
let cfsr = bench.core.system.cfsr & (mask(cfsr::NOCP) | mask(cfsr::UNDEFINSTR));
assert_eq!(cfsr, status, "{fpu} {code:x?}");
}
}
#[test]
fn cpacr_decides_who_may_use_the_extension() {
let mut bench = bench(&[]);
let refused = FAULTED;
let both = |access| set(cpacr::CP11, set(cpacr::CP10, 0, access), access);
for (cpacr, unprivileged, answer) in [
(both(0b00), false, refused),
(both(0b01), false, 0),
(both(0b01), true, refused),
(both(0b10), false, refused),
(both(0b11), true, 0),
] {
bench.core.system.cpacr = cpacr;
let npriv = set(control::NPRIV, 0, u32::from(unprivileged));
bench.core.state.control = mask(control::FPCA) | npriv;
assert_eq!(
execute_fp_check(&mut bench.core, 1, 0, 0),
answer,
"CPACR {cpacr:#x}, unprivileged {unprivileged}"
);
}
}
#[test]
fn the_first_instruction_of_a_context_takes_fpdscr() {
let one_third = |aspen: bool| {
let mut bench = Bench::new(&[0xEE80, 0x0A81]);
bench.core.system.cpacr = CPACR_FULL;
bench.core.system.fpdscr = ROUND_ZERO;
if !aspen {
bench.core.system.fpccr &= !mask(fpccr::ASPEN);
}
bench.core.state.s[1..3].copy_from_slice(&[ONE, THREE]);
bench.core.state.fpscr = mask(fpscr::N) | mask(fpscr::FZ);
bench.run(1);
bench
};
let bench = one_third(true);
let state = &bench.core.state;
assert_eq!(state.s[0], 0x3EAA_AAAA, "rounded towards zero");
assert_eq!(
state.fpscr,
mask(fpscr::N) | ROUND_ZERO | mask(fpscr::IXC),
"N kept, FZ replaced"
);
assert_eq!(state.control & mask(control::FPCA), mask(control::FPCA));
let bench = one_third(false);
assert_eq!(bench.core.state.s[0], 0x3EAA_AAAB, "FPSCR as it was");
assert_eq!(bench.core.state.control & mask(control::FPCA), 0);
let mut bench = Bench::new(&[]);
bench.core.system.cpacr = CPACR_FULL;
assert_eq!(execute_fp_check(&mut bench.core, 0, 0, 0), 0);
assert_eq!(
bench.core.state.control & mask(control::FPCA),
0,
"an UNDEFINED encoding starts no context"
);
}
fn bench_on(fpu: Fpu, code: &[u16]) -> Bench {
let model = if fpu == Fpu::FpV4Sp {
Variant::M4
} else {
Variant::M7
};
let mut bench = Bench::of(&spec_of(model, fpu, MpuRegions::Eight), code);
bench.core.system.cpacr = CPACR_FULL;
bench.core.state.control |= mask(control::FPCA);
bench
}
fn double_at(d: usize, value: u64) -> Vec<(usize, u32)> {
let [low, high] = halves(value);
vec![(2 * d, low), (2 * d + 1, high)]
}
fn single_at(s: usize, value: u32) -> Vec<(usize, u32)> {
vec![(s, value)]
}
type Case = (
&'static str,
[u16; 2],
Vec<(usize, u32)>,
u32,
Vec<(usize, u32)>,
u32,
);
fn run_cases(cases: Vec<Case>) {
for (name, code, given, mode, expected, flags) in cases {
let mut bench = bench_on(Fpu::FpV5Dp, &code);
bench.core.state.s =
std::array::from_fn(|s| 0xA5A5_0000 + u32::try_from(s).expect("one of 32"));
let mut registers = bench.core.state.s;
for (s, value) in given {
bench.core.state.s[s] = value;
registers[s] = value;
}
bench.core.state.fpscr = mode;
let (_, ran) = bench.run(1);
assert_eq!(ran, 1, "{name} retired");
for (s, value) in expected {
registers[s] = value;
}
assert_eq!(bench.core.state.s, registers, "{name}");
assert_eq!(bench.core.state.fpscr, mode | flags, "{name}'s exceptions");
}
}
const D_ONE: u64 = 0x3FF0_0000_0000_0000;
const D_TWO: u64 = 0x4000_0000_0000_0000;
const D_THREE: u64 = 0x4008_0000_0000_0000;
const D_TEN: u64 = 0x4024_0000_0000_0000;
const D_TWO_AND_A_HALF: u64 = 0x4004_0000_0000_0000;
const D_SIGN: u64 = 0x8000_0000_0000_0000;
const D_QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
const TWO_AND_A_HALF: u32 = 0x4020_0000;
const OFF: Rounding = Rounding::Fpscr;
const ZERO: Rounding = Rounding::Fixed(Round::TowardZero);
const EVEN: Rounding = Rounding::Fixed(Round::NearestTiesToEven);
const AWAY: Rounding = Rounding::Fixed(Round::NearestTiesToAway);
const UP: Rounding = Rounding::Fixed(Round::TowardPositive);
const DOWN: Rounding = Rounding::Fixed(Round::TowardNegative);
fn double_precision_cases() -> Vec<(&'static str, [u16; 2], Operation)> {
[double_arithmetic_cases(), double_conversion_cases()].concat()
}
fn double_arithmetic_cases() -> Vec<(&'static str, [u16; 2], Operation)> {
use Arithmetic::*;
let precision = Precision::Double;
let arithmetic = |operation| Operation::Arithmetic {
operation,
d: 0,
n: 2,
m: 4,
precision,
};
let moved = |sign| Operation::Move {
d: 0,
m: 2,
sign,
precision,
};
let constant = |d, value| Operation::Constant {
d,
value,
precision,
};
let compared = |m, quiet_nan_exc| Operation::Compare {
d: 0,
m,
quiet_nan_exc,
precision,
};
let integral = |rounding, exact| Operation::RoundToIntegral {
d: 0,
m: 2,
rounding,
exact,
precision,
};
let extremum = |maximum| Operation::Extremum {
maximum,
d: 0,
n: 2,
m: 4,
precision,
};
#[rustfmt::skip]
let cases = vec![
("vadd.f64 d0, d1, d2", [0xEE31, 0x0B02], arithmetic(Add)),
("vsub.f64 d0, d1, d2", [0xEE31, 0x0B42], arithmetic(Subtract)),
("vmul.f64 d0, d1, d2", [0xEE21, 0x0B02], arithmetic(Multiply)),
("vnmul.f64 d0, d1, d2", [0xEE21, 0x0B42], arithmetic(NegateMultiply)),
("vdiv.f64 d0, d1, d2", [0xEE81, 0x0B02], arithmetic(Divide)),
("vmla.f64 d0, d1, d2", [0xEE01, 0x0B02], arithmetic(MultiplyAccumulate)),
("vmls.f64 d0, d1, d2", [0xEE01, 0x0B42], arithmetic(MultiplySubtract)),
("vnmla.f64 d0, d1, d2", [0xEE11, 0x0B42], arithmetic(NegateMultiplyAccumulate)),
("vnmls.f64 d0, d1, d2", [0xEE11, 0x0B02], arithmetic(NegateMultiplySubtract)),
("vfma.f64 d0, d1, d2", [0xEEA1, 0x0B02], arithmetic(FusedMultiplyAccumulate)),
("vfms.f64 d0, d1, d2", [0xEEA1, 0x0B42], arithmetic(FusedMultiplySubtract)),
("vfnma.f64 d0, d1, d2", [0xEE91, 0x0B42], arithmetic(FusedNegateMultiplyAccumulate)),
("vfnms.f64 d0, d1, d2", [0xEE91, 0x0B02], arithmetic(FusedNegateMultiplySubtract)),
("vadd.f64 d15, d14, d13", [0xEE3E, 0xFB0D], Operation::Arithmetic { operation: Add, d: 30, n: 28, m: 26, precision }),
("vsqrt.f64 d0, d1", [0xEEB1, 0x0BC1], Operation::Arithmetic { operation: SquareRoot, d: 0, n: 2, m: 2, precision }),
("vabs.f64 d0, d1", [0xEEB0, 0x0BC1], moved(Sign::Clear)),
("vneg.f64 d0, d1", [0xEEB1, 0x0B41], moved(Sign::Flip)),
("vmov.f64 d0, d1", [0xEEB0, 0x0B41], moved(Sign::Keep)),
("vmov.f64 d0, #1.0", [0xEEB7, 0x0B00], constant(0, D_ONE)),
("vmov.f64 d15, #-0.125", [0xEEBC, 0xFB00], constant(30, 0xBFC0_0000_0000_0000)),
("vcmp.f64 d0, d1", [0xEEB4, 0x0B41], compared(Some(2), false)),
("vcmpe.f64 d0, d1", [0xEEB4, 0x0BC1], compared(Some(2), true)),
("vcmp.f64 d0, #0", [0xEEB5, 0x0B40], compared(None, false)),
("vcmpe.f64 d0, #0", [0xEEB5, 0x0BC0], compared(None, true)),
("vrintz.f64 d0, d1", [0xEEB6, 0x0BC1], integral(ZERO, false)),
("vrintr.f64 d0, d1", [0xEEB6, 0x0B41], integral(OFF, false)),
("vrintx.f64 d0, d1", [0xEEB7, 0x0B41], integral(OFF, true)),
("vrinta.f64 d0, d1", [0xFEB8, 0x0B41], integral(AWAY, false)),
("vrintn.f64 d0, d1", [0xFEB9, 0x0B41], integral(EVEN, false)),
("vrintp.f64 d0, d1", [0xFEBA, 0x0B41], integral(UP, false)),
("vrintm.f64 d0, d1", [0xFEBB, 0x0B41], integral(DOWN, false)),
("vselgt.f64 d0, d1, d2", [0xFE31, 0x0B02], Operation::Select { d: 0, n: 2, m: 4, cond: 0b1100, precision }),
("vmaxnm.f64 d0, d1, d2", [0xFE81, 0x0B02], extremum(true)),
("vminnm.f64 d0, d1, d2", [0xFE81, 0x0B42], extremum(false)),
];
cases
}
fn double_conversion_cases() -> Vec<(&'static str, [u16; 2], Operation)> {
let (double, single) = (Precision::Double, Precision::Single);
let to = |m, format, rounding, result| Operation::ToFixed {
d: 0,
m,
format,
rounding,
source: double,
result,
};
let from = |m, format, rounding| Operation::FromFixed {
d: 0,
m,
format,
rounding,
result: double,
};
let from_half = |top| Operation::FromHalf {
d: 0,
m: 1,
top,
result: double,
};
let to_half = |top| Operation::ToHalf {
d: 0,
m: 2,
top,
source: double,
};
#[rustfmt::skip]
let cases = vec![
("vcvt.s32.f64 s0, d1", [0xEEBD, 0x0BC1], to(2, integer(true), ZERO, single)),
("vcvtr.s32.f64 s0, d1", [0xEEBD, 0x0B41], to(2, integer(true), OFF, single)),
("vcvt.u32.f64 s0, d1", [0xEEBC, 0x0BC1], to(2, integer(false), ZERO, single)),
("vcvtr.u32.f64 s0, d1", [0xEEBC, 0x0B41], to(2, integer(false), OFF, single)),
("vcvta.s32.f64 s0, d1", [0xFEBC, 0x0BC1], to(2, integer(true), AWAY, single)),
("vcvtm.u32.f64 s0, d1", [0xFEBF, 0x0B41], to(2, integer(false), DOWN, single)),
("vcvt.f64.s32 d0, s1", [0xEEB8, 0x0BE0], from(1, integer(true), OFF)),
("vcvt.f64.u32 d0, s1", [0xEEB8, 0x0B60], from(1, integer(false), OFF)),
("vcvt.f64.s32 d0, s17", [0xEEB8, 0x0BE8], from(17, integer(true), OFF)),
("vcvt.s16.f64 d0, d0, #8", [0xEEBE, 0x0B44], Operation::ToFixed { d: 0, m: 0, format: fixed(16, 8, true), rounding: ZERO, source: double, result: double }),
("vcvt.u32.f64 d0, d0, #32", [0xEEBF, 0x0BC0], Operation::ToFixed { d: 0, m: 0, format: fixed(32, 32, false), rounding: ZERO, source: double, result: double }),
("vcvt.f64.s16 d0, d0, #8", [0xEEBA, 0x0B44], from(0, fixed(16, 8, true), EVEN)),
("vcvt.f64.u32 d0, d0, #32", [0xEEBB, 0x0BC0], from(0, fixed(32, 32, false), EVEN)),
("vcvt.f64.f32 d0, s1", [0xEEB7, 0x0AE0], Operation::Convert { d: 0, m: 1, result: double }),
("vcvt.f32.f64 s0, d1", [0xEEB7, 0x0BC1], Operation::Convert { d: 0, m: 2, result: single }),
("vcvtb.f64.f16 d0, s1", [0xEEB2, 0x0B60], from_half(false)),
("vcvtt.f64.f16 d0, s1", [0xEEB2, 0x0BE0], from_half(true)),
("vcvtb.f16.f64 s0, d1", [0xEEB3, 0x0B41], to_half(false)),
("vcvtt.f16.f64 s0, d1", [0xEEB3, 0x0BC1], to_half(true)),
];
cases
}
fn fpv5_single_cases() -> Vec<(&'static str, [u16; 2], Operation)> {
let precision = Precision::Single;
let integral = |rounding, exact| Operation::RoundToIntegral {
d: 0,
m: 1,
rounding,
exact,
precision,
};
let to = |signed, rounding| Operation::ToFixed {
d: 0,
m: 1,
format: integer(signed),
rounding,
source: precision,
result: precision,
};
let select = |cond| Operation::Select {
d: 0,
n: 1,
m: 2,
cond,
precision,
};
let extremum = |maximum| Operation::Extremum {
maximum,
d: 0,
n: 1,
m: 2,
precision,
};
vec![
("vrintz.f32 s0, s1", [0xEEB6, 0x0AE0], integral(ZERO, false)),
("vrintr.f32 s0, s1", [0xEEB6, 0x0A60], integral(OFF, false)),
("vrintx.f32 s0, s1", [0xEEB7, 0x0A60], integral(OFF, true)),
("vrinta.f32 s0, s1", [0xFEB8, 0x0A60], integral(AWAY, false)),
("vrintn.f32 s0, s1", [0xFEB9, 0x0A60], integral(EVEN, false)),
("vrintp.f32 s0, s1", [0xFEBA, 0x0A60], integral(UP, false)),
("vrintm.f32 s0, s1", [0xFEBB, 0x0A60], integral(DOWN, false)),
("vcvta.s32.f32 s0, s1", [0xFEBC, 0x0AE0], to(true, AWAY)),
("vcvtn.s32.f32 s0, s1", [0xFEBD, 0x0AE0], to(true, EVEN)),
("vcvtp.u32.f32 s0, s1", [0xFEBE, 0x0A60], to(false, UP)),
("vcvtm.u32.f32 s0, s1", [0xFEBF, 0x0A60], to(false, DOWN)),
("vseleq.f32 s0, s1, s2", [0xFE00, 0x0A81], select(0b0000)),
("vselvs.f32 s0, s1, s2", [0xFE10, 0x0A81], select(0b0110)),
("vselge.f32 s0, s1, s2", [0xFE20, 0x0A81], select(0b1010)),
("vselgt.f32 s0, s1, s2", [0xFE30, 0x0A81], select(0b1100)),
("vmaxnm.f32 s0, s1, s2", [0xFE80, 0x0A81], extremum(true)),
("vminnm.f32 s0, s1, s2", [0xFE80, 0x0AC1], extremum(false)),
]
}
#[test]
fn every_fpv5_encoding_decodes_to_its_instruction() {
for (name, [hw1, hw2], expected) in double_precision_cases() {
assert_eq!(
decoded_on(Fpu::FpV5Dp, hw1, hw2),
Decoded::Operation(expected),
"{name}"
);
}
for fpu in [Fpu::FpV5Sp, Fpu::FpV5Dp] {
for (name, [hw1, hw2], expected) in fpv5_single_cases() {
assert_eq!(
decoded_on(fpu, hw1, hw2),
Decoded::Operation(expected),
"{name} on {fpu}"
);
}
}
}
#[test]
fn a_unit_refuses_what_it_hasnt_got() {
for fpu in [Fpu::FpV4Sp, Fpu::FpV5Sp] {
for (name, [hw1, hw2], _) in double_precision_cases() {
assert_eq!(
decoded_on(fpu, hw1, hw2),
Decoded::Undefined,
"{name} on {fpu}"
);
}
}
for (name, [hw1, hw2], _) in fpv5_single_cases() {
assert_eq!(decoded(hw1, hw2), Decoded::Undefined, "{name} on FPv4-SP");
}
let cases = [
("vsel with bit 6 set", [0xFE00, 0x0AC1]),
("T set, opc1 1x01", [0xFE90, 0x0A81]),
("vrinta with opc3 11", [0xFEB8, 0x0AE0]),
("T set, opc2 01xx", [0xFEB4, 0x0A60]),
("vldr, T set", [0xFD90, 0x2A02]),
("vmov s3, r2, T set", [0xFE01, 0x2A90]),
];
for (name, [hw1, hw2]) in cases {
assert_eq!(
decoded_on(Fpu::FpV5Dp, hw1, hw2),
Decoded::Undefined,
"{name}"
);
}
}
#[test]
fn a_double_naming_d16_up_checks_the_context_before_it_is_undefined() {
let cases = [
("vadd.f64 d16, d1, d2", [0xEE71, 0x0B02]),
("vadd.f64 d0, d17, d2", [0xEE31, 0x0B82]),
("vadd.f64 d0, d1, d18", [0xEE31, 0x0B22]),
("vmov.f64 d16, #1.0", [0xEEF7, 0x0B00]),
("vcmp.f64 d16, #0", [0xEEF5, 0x0B40]),
("vcvt.f64.f32 d16, s1", [0xEEF7, 0x0AE0]),
("vcvt.s32.f64 s0, d17", [0xEEBD, 0x0BE1]),
("vcvtb.f16.f64 s0, d17", [0xEEB3, 0x0B61]),
("vrinta.f64 d16, d1", [0xFEF8, 0x0B41]),
("vselgt.f64 d0, d17, d2", [0xFE31, 0x0B82]),
("vmaxnm.f64 d0, d1, d18", [0xFE81, 0x0B22]),
];
for (name, [hw1, hw2]) in cases {
assert_eq!(
decoded_on(Fpu::FpV5Dp, hw1, hw2),
Decoded::Operation(Operation::BeyondBank),
"{name}"
);
}
for (fpu, checked) in [(Fpu::FpV5Dp, ROUND_ZERO), (Fpu::FpV5Sp, 0)] {
let mut bench = bench_on(fpu, &[0xEE71, 0x0B02]);
bench.core.state.control &= !mask(control::FPCA);
bench.core.system.fpdscr = ROUND_ZERO;
bench.run(1);
assert_eq!(
bench.core.system.cfsr & mask(cfsr::UNDEFINSTR),
mask(cfsr::UNDEFINSTR),
"{fpu}"
);
assert_eq!(bench.core.state.fpscr, checked, "{fpu}");
}
}
#[test]
fn the_unconditional_encodings_are_undefined_in_an_it_block() {
for (name, code, status) in [
(
"vseleq.f32 s0, s1, s2",
[0xBF08, 0xFE00, 0x0A81],
mask(cfsr::UNDEFINSTR),
),
(
"vrinta.f32 s0, s1",
[0xBF08, 0xFEB8, 0x0A60],
mask(cfsr::UNDEFINSTR),
),
("vrintx.f32 s0, s1", [0xBF08, 0xEEB7, 0x0A60], 0),
] {
let mut bench = bench_on(Fpu::FpV5Sp, &code);
bench.core.state.z = 1;
bench.run(2);
assert_eq!(
bench.core.system.cfsr & mask(cfsr::UNDEFINSTR),
status,
"it eq; {name}"
);
}
}
#[test]
fn each_unit_runs_only_its_own_instructions() {
let vadd_f64 = [0xEE31, 0x0B02];
let vrintx = [0xEEB7, 0x0A60];
let vcvt_f64_f32 = [0xEEB7, 0x0AE0];
let vselgt = [0xFE30, 0x0A81];
for (fpu, code, status) in [
(Fpu::FpV4Sp, vadd_f64, mask(cfsr::UNDEFINSTR)),
(Fpu::FpV5Sp, vadd_f64, mask(cfsr::UNDEFINSTR)),
(Fpu::FpV5Dp, vadd_f64, 0),
(Fpu::FpV4Sp, vrintx, mask(cfsr::UNDEFINSTR)),
(Fpu::FpV5Sp, vrintx, 0),
(Fpu::FpV5Sp, vcvt_f64_f32, mask(cfsr::UNDEFINSTR)),
(Fpu::FpV5Dp, vcvt_f64_f32, 0),
(Fpu::FpV4Sp, vselgt, mask(cfsr::UNDEFINSTR)),
(Fpu::FpV5Sp, vselgt, 0),
] {
let mut bench = bench_on(fpu, &code);
bench.run(1);
assert_eq!(
bench.core.system.cfsr & mask(cfsr::UNDEFINSTR),
status,
"{fpu} {code:x?}"
);
}
}
#[test]
fn every_double_arithmetic_instruction_computes_its_pseudocode() {
let operands = [
double_at(0, D_TEN),
double_at(1, D_TWO),
double_at(2, D_THREE),
]
.concat();
let case = |name, code, result, flags| -> Case {
(name, code, operands.clone(), 0, double_at(0, result), flags)
};
let ixc = mask(fpscr::IXC);
run_cases(vec![
case("vmla.f64", [0xEE01, 0x0B02], 0x4030_0000_0000_0000, 0),
case("vmls.f64", [0xEE01, 0x0B42], 0x4010_0000_0000_0000, 0),
case("vnmla.f64", [0xEE11, 0x0B42], 0xC030_0000_0000_0000, 0),
case("vnmls.f64", [0xEE11, 0x0B02], 0xC010_0000_0000_0000, 0),
case("vmul.f64", [0xEE21, 0x0B02], 0x4018_0000_0000_0000, 0),
case("vnmul.f64", [0xEE21, 0x0B42], 0xC018_0000_0000_0000, 0),
case("vadd.f64", [0xEE31, 0x0B02], 0x4014_0000_0000_0000, 0),
case("vsub.f64", [0xEE31, 0x0B42], D_SIGN | D_ONE, 0),
case("vdiv.f64", [0xEE81, 0x0B02], 0x3FE5_5555_5555_5555, ixc),
case("vfma.f64", [0xEEA1, 0x0B02], 0x4030_0000_0000_0000, 0),
case("vfms.f64", [0xEEA1, 0x0B42], 0x4010_0000_0000_0000, 0),
case("vfnma.f64", [0xEE91, 0x0B42], 0xC030_0000_0000_0000, 0),
case("vfnms.f64", [0xEE91, 0x0B02], 0xC010_0000_0000_0000, 0),
case(
"vsqrt.f64 d0, d1",
[0xEEB1, 0x0BC1],
0x3FF6_A09E_667F_3BCD,
ixc,
),
(
"vadd.f64 d15, d14, d13",
[0xEE3E, 0xFB0D],
[double_at(14, D_ONE), double_at(13, D_TWO)].concat(),
0,
double_at(15, D_THREE),
0,
),
(
"vmaxnm.f64 d0, d1, d2",
[0xFE81, 0x0B02],
[double_at(1, D_ONE), double_at(2, D_ONE + 1)].concat(),
0,
double_at(0, D_ONE + 1),
0,
),
(
"vminnm.f64 d0, d1, d2 of a quiet NaN",
[0xFE81, 0x0B42],
[double_at(1, D_QUIET_NAN), double_at(2, D_TWO)].concat(),
0,
double_at(0, D_TWO),
0,
),
]);
}
#[test]
fn double_moves_and_compares_take_both_words() {
let signaling = 0xFFF0_0000_0000_0001;
run_cases(vec![
(
"vmov.f64 d0, d1",
[0xEEB0, 0x0B41],
double_at(1, signaling),
mask(fpscr::FZ),
double_at(0, signaling),
0,
),
(
"vabs.f64 d0, d1",
[0xEEB0, 0x0BC1],
double_at(1, signaling),
mask(fpscr::FZ),
double_at(0, signaling & !D_SIGN),
0,
),
(
"vneg.f64 d0, d1",
[0xEEB1, 0x0B41],
double_at(1, 1),
mask(fpscr::FZ),
double_at(0, D_SIGN | 1),
0,
),
(
"vmov.f64 d15, #-0.125",
[0xEEBC, 0xFB00],
vec![],
0,
double_at(15, 0xBFC0_0000_0000_0000),
0,
),
(
"vcmp.f64 d0, d1, by the low word",
[0xEEB4, 0x0B41],
[double_at(0, D_ONE + 1), double_at(1, D_ONE)].concat(),
0,
vec![],
mask(fpscr::C),
),
(
"vcmp.f64 d0, d1, less",
[0xEEB4, 0x0B41],
[double_at(0, D_ONE), double_at(1, D_TWO)].concat(),
0,
vec![],
mask(fpscr::N),
),
(
"vcmpe.f64 d0, d1, unordered",
[0xEEB4, 0x0BC1],
[double_at(0, D_QUIET_NAN), double_at(1, D_ONE)].concat(),
0,
vec![],
UNORDERED | mask(fpscr::IOC),
),
(
"vcmp.f64 d0, #0 of -0",
[0xEEB5, 0x0B40],
double_at(0, D_SIGN),
0,
vec![],
EQUAL,
),
]);
}
#[test]
fn every_double_conversion_converts_with_its_rounding() {
#[rustfmt::skip]
run_cases(vec![
("vcvt.s32.f64 s0, d1", [0xEEBD, 0x0BC1], double_at(1, D_TWO_AND_A_HALF), ROUND_UP, single_at(0, 2), mask(fpscr::IXC)),
("vcvtr.s32.f64 s0, d1", [0xEEBD, 0x0B41], double_at(1, D_TWO_AND_A_HALF), ROUND_UP, single_at(0, 3), mask(fpscr::IXC)),
("vcvt.u32.f64 s0, d1", [0xEEBC, 0x0BC1], double_at(1, D_SIGN | D_ONE), 0, single_at(0, 0), mask(fpscr::IOC)),
("vcvt.f64.s32 d0, s1", [0xEEB8, 0x0BE0], single_at(1, u32::MAX), 0, double_at(0, D_SIGN | D_ONE), 0),
("vcvt.f64.u32 d0, s1, exact", [0xEEB8, 0x0B60], single_at(1, u32::MAX), ROUND_ZERO, double_at(0, 0x41EF_FFFF_FFE0_0000), 0),
("vcvt.s16.f64 d0, d0, #8", [0xEEBE, 0x0B44], double_at(0, 0xBFF8_0000_0000_0000), 0, double_at(0, 0xFFFF_FFFF_FFFF_FE80), 0),
("vcvt.u32.f64 d0, d0, #32", [0xEEBF, 0x0BC0], double_at(0, 0x3FD0_0000_0000_0000), 0, double_at(0, 0x4000_0000), 0),
("vcvt.f64.s16 d0, d0, #8", [0xEEBA, 0x0B44], double_at(0, 0xDEAD_BEEF_1234_FE80), 0, double_at(0, 0xBFF8_0000_0000_0000), 0),
("vcvt.f64.u32 d0, d0, #32", [0xEEBB, 0x0BC0], double_at(0, 0xDEAD_BEEF_FFFF_FFFF), 0, double_at(0, 0x3FEF_FFFF_FFE0_0000), 0),
("vcvt.f64.f32 d0, s1", [0xEEB7, 0x0AE0], single_at(1, 0x3EAA_AAAB), 0, double_at(0, 0x3FD5_5555_6000_0000), 0),
("vcvt.f32.f64 s0, d1", [0xEEB7, 0x0BC1], double_at(1, 0x3FD5_5555_5555_5555), 0, single_at(0, 0x3EAA_AAAB), mask(fpscr::IXC)),
("vcvtb.f64.f16 d0, s1", [0xEEB2, 0x0B60], single_at(1, 0xC000_3C00), 0, double_at(0, D_ONE), 0),
("vcvtt.f64.f16 d0, s1", [0xEEB2, 0x0BE0], single_at(1, 0xC000_3C00), 0, double_at(0, D_SIGN | D_TWO), 0),
("vcvtb.f16.f64 s0, d1", [0xEEB3, 0x0B41], [single_at(0, 0xAAAA_5555), double_at(1, D_ONE)].concat(), 0, single_at(0, 0xAAAA_3C00), 0),
("vcvtt.f16.f64 s0, d1", [0xEEB3, 0x0BC1], [single_at(0, 0xAAAA_5555), double_at(1, D_ONE)].concat(), 0, single_at(0, 0x3C00_5555), 0),
]);
}
#[test]
fn every_vrint_rounds_to_integral_by_its_mode() {
let minus = 0xC020_0000;
#[rustfmt::skip]
run_cases(vec![
("vrinta.f32", [0xFEB8, 0x0A60], single_at(1, TWO_AND_A_HALF), 0, single_at(0, THREE), 0),
("vrintn.f32", [0xFEB9, 0x0A60], single_at(1, TWO_AND_A_HALF), ROUND_UP, single_at(0, TWO), 0),
("vrintp.f32", [0xFEBA, 0x0A60], single_at(1, minus), 0, single_at(0, 0xC000_0000), 0),
("vrintm.f32", [0xFEBB, 0x0A60], single_at(1, minus), 0, single_at(0, 0xC040_0000), 0),
("vrintz.f32", [0xEEB6, 0x0AE0], single_at(1, minus), ROUND_UP, single_at(0, 0xC000_0000), 0),
("vrintr.f32", [0xEEB6, 0x0A60], single_at(1, TWO_AND_A_HALF), ROUND_UP, single_at(0, THREE), 0),
("vrintx.f32", [0xEEB7, 0x0A60], single_at(1, TWO_AND_A_HALF), 0, single_at(0, TWO), mask(fpscr::IXC)),
("vrinta.f64", [0xFEB8, 0x0B41], double_at(1, D_TWO_AND_A_HALF), 0, double_at(0, D_THREE), 0),
("vrintm.f64", [0xFEBB, 0x0B41], double_at(1, D_SIGN | D_TWO_AND_A_HALF), 0, double_at(0, D_SIGN | D_THREE), 0),
("vrintx.f64", [0xEEB7, 0x0B41], double_at(1, D_TWO_AND_A_HALF), ROUND_ZERO, double_at(0, D_TWO), mask(fpscr::IXC)),
("vrintz.f64", [0xEEB6, 0x0BC1], double_at(1, 0x432F_FFFF_FFFF_FFFF), 0, double_at(0, 0x432F_FFFF_FFFF_FFFE), 0),
]);
}
#[test]
fn vcvta_to_vcvtm_convert_by_their_own_mode() {
#[rustfmt::skip]
run_cases(vec![
("vcvta.s32.f32", [0xFEBC, 0x0AE0], single_at(1, 0xC020_0000), 0, single_at(0, 0xFFFF_FFFD), mask(fpscr::IXC)),
("vcvtn.s32.f32", [0xFEBD, 0x0AE0], single_at(1, TWO_AND_A_HALF), ROUND_UP, single_at(0, 2), mask(fpscr::IXC)),
("vcvtp.u32.f32", [0xFEBE, 0x0A60], single_at(1, 0x4006_6666), 0, single_at(0, 3), mask(fpscr::IXC)),
("vcvtm.u32.f32", [0xFEBF, 0x0A60], single_at(1, 0xBF00_0000), 0, single_at(0, 0), mask(fpscr::IOC)),
("vcvta.s32.f64", [0xFEBC, 0x0BC1], double_at(1, D_TWO_AND_A_HALF), 0, single_at(0, 3), mask(fpscr::IXC)),
("vcvtm.u32.f64", [0xFEBF, 0x0B41], double_at(1, 0x4202_A05F_2000_0000), 0, single_at(0, u32::MAX), mask(fpscr::IOC)),
("vcvta.s32.f32 of a NaN", [0xFEBC, 0x0AE0], single_at(1, QUIET_NAN), 0, single_at(0, 0), mask(fpscr::IOC)),
]);
}
#[test]
fn vsel_takes_the_first_operand_where_its_condition_holds() {
let vselgt_f64 = [0xFE31, 0x0B02];
for (name, code, [n, z, v], first) in [
("vseleq", [0xFE00, 0x0A81], [0, 1, 0], true),
("vseleq", [0xFE00, 0x0A81], [0, 0, 0], false),
("vselvs", [0xFE10, 0x0A81], [0, 0, 1], true),
("vselvs", [0xFE10, 0x0A81], [0, 0, 0], false),
("vselge", [0xFE20, 0x0A81], [1, 0, 1], true),
("vselge", [0xFE20, 0x0A81], [1, 0, 0], false),
("vselgt", [0xFE30, 0x0A81], [0, 0, 0], true),
("vselgt", [0xFE30, 0x0A81], [0, 1, 0], false),
("vselgt.f64", vselgt_f64, [0, 0, 0], true),
("vselgt.f64", vselgt_f64, [1, 0, 0], false),
] {
let mut bench = bench_on(Fpu::FpV5Dp, &code);
bench.core.state.s[..6].copy_from_slice(&[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]);
(bench.core.state.n, bench.core.state.z, bench.core.state.v) = (n, z, v);
bench.run(1);
let state = &bench.core.state;
let expected = match (code == vselgt_f64, first) {
(false, true) => [0x11, 0x11],
(false, false) => [0x12, 0x11],
(true, true) => [0x12, 0x13],
(true, false) => [0x14, 0x15],
};
assert_eq!(state.s[..2], expected, "{name} with N {n}, Z {z}, V {v}");
}
}
}