use cranelift_codegen::ir::{InstBuilder, Value, condcodes::IntCC, types};
use super::{Emitter, bits};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ShiftType {
Lsl,
Lsr,
Asr,
Ror,
Rrx,
}
pub(crate) fn add_with_carry(
e: &mut Emitter<'_, '_>,
x: Value,
y: Value,
carry_in: Value,
) -> (Value, Value, Value) {
let carry = e.b.ins().uextend(types::I64, carry_in);
let (ux, uy) = (
e.b.ins().uextend(types::I64, x),
e.b.ins().uextend(types::I64, y),
);
let unsigned_sum = e.b.ins().iadd(ux, uy);
let unsigned_sum = e.b.ins().iadd(unsigned_sum, carry);
let (sx, sy) = (
e.b.ins().sextend(types::I64, x),
e.b.ins().sextend(types::I64, y),
);
let signed_sum = e.b.ins().iadd(sx, sy);
let signed_sum = e.b.ins().iadd(signed_sum, carry);
let result = e.b.ins().ireduce(types::I32, unsigned_sum);
let unsigned = e.b.ins().uextend(types::I64, result);
let carry_out = e.b.ins().icmp(IntCC::NotEqual, unsigned, unsigned_sum);
let signed = e.b.ins().sextend(types::I64, result);
let overflow = e.b.ins().icmp(IntCC::NotEqual, signed, signed_sum);
(result, word(e, carry_out), word(e, overflow))
}
pub(crate) fn decode_imm_shift(shift_type: u32, imm5: u32) -> (ShiftType, u32) {
match (shift_type & 0b11, imm5) {
(0b00, _) => (ShiftType::Lsl, imm5),
(0b01, 0) => (ShiftType::Lsr, 32),
(0b01, _) => (ShiftType::Lsr, imm5),
(0b10, 0) => (ShiftType::Asr, 32),
(0b10, _) => (ShiftType::Asr, imm5),
(_, 0) => (ShiftType::Rrx, 1),
_ => (ShiftType::Ror, imm5),
}
}
pub(crate) fn shift_c(
e: &mut Emitter<'_, '_>,
value: Value,
shift_type: ShiftType,
amount: u32,
carry_in: Value,
) -> (Value, Value) {
assert!(
shift_type != ShiftType::Rrx || amount == 1,
"Shift_C only shifts RRX by one"
);
if amount == 0 {
return (value, carry_in);
}
let shift = i64::from(amount);
match (shift_type, amount) {
(ShiftType::Lsl, 1..=31) => {
let result = e.b.ins().ishl_imm_s(value, shift);
(result, bit(e, value, 32 - amount))
}
(ShiftType::Lsl, 32) => (e.constant(0), bit(e, value, 0)),
(ShiftType::Lsr, 1..=31) => {
let result = e.b.ins().ushr_imm_u(value, shift);
(result, bit(e, value, amount - 1))
}
(ShiftType::Lsr, 32) => (e.constant(0), bit(e, value, 31)),
(ShiftType::Lsl | ShiftType::Lsr, _) => (e.constant(0), e.constant(0)),
(ShiftType::Asr, 1..=31) => {
let result = e.b.ins().sshr_imm_u(value, shift);
(result, bit(e, value, amount - 1))
}
(ShiftType::Asr, _) => {
let result = e.b.ins().sshr_imm_u(value, 31);
(result, bit(e, value, 31))
}
(ShiftType::Ror, _) => {
let result = e.b.ins().rotr_imm_u(value, i64::from(amount % 32));
(result, bit(e, result, 31))
}
(ShiftType::Rrx, _) => {
let top = e.b.ins().ishl_imm_s(carry_in, 31);
let rest = e.b.ins().ushr_imm_u(value, 1);
let result = e.b.ins().bor(top, rest);
(result, bit(e, value, 0))
}
}
}
pub(crate) fn shift_c_register(
e: &mut Emitter<'_, '_>,
value: Value,
shift_type: ShiftType,
amount: Value,
carry_in: Value,
) -> (Value, Value) {
let clamped = e.constant(33);
let clamped = e.b.ins().umin(amount, clamped);
let (result, carry) = match shift_type {
ShiftType::Lsl => {
let wide = e.b.ins().uextend(types::I64, value);
let extended = e.b.ins().ishl(wide, clamped);
let result = e.b.ins().ireduce(types::I32, extended);
let carry = e.b.ins().ushr_imm_u(extended, 32);
let carry = e.b.ins().ireduce(types::I32, carry);
(result, e.b.ins().band_imm_s(carry, 1))
}
ShiftType::Lsr | ShiftType::Asr => {
let wide = if shift_type == ShiftType::Lsr {
e.b.ins().uextend(types::I64, value)
} else {
e.b.ins().sextend(types::I64, value)
};
let below = e.b.ins().ishl_imm_s(wide, 1);
let (shifted, out) = if shift_type == ShiftType::Lsr {
(
e.b.ins().ushr(wide, clamped),
e.b.ins().ushr(below, clamped),
)
} else {
(
e.b.ins().sshr(wide, clamped),
e.b.ins().sshr(below, clamped),
)
};
let result = e.b.ins().ireduce(types::I32, shifted);
let carry = e.b.ins().ireduce(types::I32, out);
(result, e.b.ins().band_imm_s(carry, 1))
}
ShiftType::Ror => {
let result = e.b.ins().rotr(value, amount);
(result, bit(e, result, 31))
}
ShiftType::Rrx => panic!("no register shift is RRX"),
};
let zero = e.b.ins().icmp_imm_s(IntCC::Equal, amount, 0);
(
e.b.ins().select(zero, value, result),
e.b.ins().select(zero, carry_in, carry),
)
}
pub(crate) fn thumb_expand_imm_c(imm12: u32) -> (u32, Option<u32>) {
if bits(imm12, 11, 10) != 0b00 {
let unrotated = 0x80 | bits(imm12, 6, 0);
let imm32 = unrotated.rotate_right(bits(imm12, 11, 7));
return (imm32, Some(imm32 >> 31));
}
let imm8 = bits(imm12, 7, 0);
let imm32 = match bits(imm12, 9, 8) {
0b00 => imm8,
0b01 => imm8 << 16 | imm8,
0b10 => imm8 << 24 | imm8 << 8,
_ => imm8 * 0x0101_0101,
};
(imm32, None)
}
pub(crate) fn signed_sat_q(e: &mut Emitter<'_, '_>, i: Value, n: u32) -> (Value, Value) {
assert!(
(1..=32).contains(&n),
"SignedSatQ saturates to 1 to 32 bits"
);
let largest = (1_i64 << (n - 1)) - 1;
let (result, saturated) = saturate(e, i, -largest - 1, largest);
let result = if n < 32 {
e.b.ins()
.band_imm_u(result, i64::from(u32::MAX >> (32 - n)))
} else {
result
};
(result, saturated)
}
pub(crate) fn unsigned_sat_q(e: &mut Emitter<'_, '_>, i: Value, n: u32) -> (Value, Value) {
assert!(n <= 32, "UnsignedSatQ saturates to at most 32 bits");
saturate(e, i, 0, (1_i64 << n) - 1)
}
fn saturate(e: &mut Emitter<'_, '_>, i: Value, smallest: i64, largest: i64) -> (Value, Value) {
let above = e.b.ins().icmp_imm_s(IntCC::SignedGreaterThan, i, largest);
let below = e.b.ins().icmp_imm_s(IntCC::SignedLessThan, i, smallest);
let high = e.b.ins().iconst(types::I64, largest);
let low = e.b.ins().iconst(types::I64, smallest);
let result = e.b.ins().select(below, low, i);
let result = e.b.ins().select(above, high, result);
let saturated = e.b.ins().bor(above, below);
(e.b.ins().ireduce(types::I32, result), word(e, saturated))
}
fn bit(e: &mut Emitter<'_, '_>, value: Value, n: u32) -> Value {
let shifted = e.b.ins().ushr_imm_u(value, i64::from(n));
e.b.ins().band_imm_s(shifted, 1)
}
fn word(e: &mut Emitter<'_, '_>, truth: Value) -> Value {
e.b.ins().uextend(types::I32, truth)
}
#[cfg(test)]
mod tests {
use cranelift_codegen::ir::{self, DataFlowGraph, InstructionData, Opcode, ValueDef};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use super::*;
fn emitted(build: impl FnOnce(&mut Emitter<'_, '_>) -> Vec<Value>) -> Vec<u32> {
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 values = build(&mut e);
values
.into_iter()
.map(|value| u32::try_from(evaluate(&builder.func.dfg, value)).unwrap())
.collect()
}
fn evaluate(dfg: &DataFlowGraph, value: Value) -> u64 {
let value = dfg.resolve_aliases(value);
let ValueDef::Result(inst, _) = dfg.value_def(value) else {
panic!("{value} isn't an instruction's");
};
let width = dfg.value_type(value).bits();
let args = dfg.inst_args(inst);
let arg = |k: usize| evaluate(dfg, args[k]);
let arg_width = |k: usize| dfg.value_type(args[k]).bits();
let sign = |x: u64, width: u32| (x << (64 - width)).cast_signed() >> (64 - width);
let amount = |k: usize| arg(k) % u64::from(width);
let answer = match dfg.insts[inst] {
InstructionData::UnaryImm { imm, .. } => imm.bits().cast_unsigned(),
InstructionData::IntCompare { cond, .. } => {
let (x, y) = (arg(0), arg(1));
let (sx, sy) = (sign(x, arg_width(0)), sign(y, arg_width(1)));
u64::from(match cond {
IntCC::Equal => x == y,
IntCC::NotEqual => x != y,
IntCC::SignedGreaterThan => sx > sy,
IntCC::SignedLessThan => sx < sy,
other => panic!("the evaluator doesn't compare {other}"),
})
}
data => match data.opcode() {
Opcode::Iadd => arg(0).wrapping_add(arg(1)),
Opcode::Band => arg(0) & arg(1),
Opcode::Bor => arg(0) | arg(1),
Opcode::Ishl => arg(0) << amount(1),
Opcode::Ushr => arg(0) >> amount(1),
Opcode::Sshr => (sign(arg(0), width) >> amount(1)).cast_unsigned(),
Opcode::Rotr => match amount(1) {
0 => arg(0),
by => arg(0) >> by | arg(0) << (u64::from(width) - by),
},
Opcode::Umin => arg(0).min(arg(1)),
Opcode::Uextend | Opcode::Ireduce => arg(0),
Opcode::Sextend => sign(arg(0), arg_width(0)).cast_unsigned(),
Opcode::Select => {
if arg(0) == 0 {
arg(2)
} else {
arg(1)
}
}
other => panic!("the evaluator doesn't run {other}"),
},
};
answer & (u64::MAX >> (64 - width))
}
fn add(x: u32, y: u32, carry_in: u32) -> (u32, u32, u32) {
let answer = emitted(|e| {
let (x, y, carry_in) = (e.constant(x), e.constant(y), e.constant(carry_in));
let (result, carry, overflow) = add_with_carry(e, x, y, carry_in);
vec![result, carry, overflow]
});
(answer[0], answer[1], answer[2])
}
fn shift(value: u32, shift_type: ShiftType, amount: u32, carry_in: u32) -> (u32, u32) {
let answer = emitted(|e| {
let (value, carry_in) = (e.constant(value), e.constant(carry_in));
let (result, carry) = shift_c(e, value, shift_type, amount, carry_in);
vec![result, carry]
});
(answer[0], answer[1])
}
fn shift_register(value: u32, shift_type: ShiftType, amount: u32, carry_in: u32) -> (u32, u32) {
let answer = emitted(|e| {
let (value, carry_in) = (e.constant(value), e.constant(carry_in));
let amount = e.constant(amount);
let (result, carry) = shift_c_register(e, value, shift_type, amount, carry_in);
vec![result, carry]
});
(answer[0], answer[1])
}
fn signed(i: i64, n: u32) -> (u32, u32) {
let answer = emitted(|e| {
let i = e.b.ins().iconst(types::I64, i);
let (result, saturated) = signed_sat_q(e, i, n);
vec![result, saturated]
});
(answer[0], answer[1])
}
fn unsigned(i: i64, n: u32) -> (u32, u32) {
let answer = emitted(|e| {
let i = e.b.ins().iconst(types::I64, i);
let (result, saturated) = unsigned_sat_q(e, i, n);
vec![result, saturated]
});
(answer[0], answer[1])
}
#[test]
fn add_with_carry_sets_carry_and_overflow_as_the_pseudocode_does() {
assert_eq!(add(1, 2, 0), (3, 0, 0));
assert_eq!(add(0xFFFF_FFFF, 1, 0), (0, 1, 0));
assert_eq!(add(0x7FFF_FFFF, 1, 0), (0x8000_0000, 0, 1));
assert_eq!(add(0x8000_0000, 0x8000_0000, 0), (0, 1, 1));
assert_eq!(add(0xFFFF_FFFF, 0, 1), (0, 1, 0), "the carry in adds one");
assert_eq!(add(0x7FFF_FFFF, 0, 1), (0x8000_0000, 0, 1));
assert_eq!(add(0xFFFF_FFFF, 0xFFFF_FFFF, 1), (0xFFFF_FFFF, 1, 0));
assert_eq!(add(5, !5, 1), (0, 1, 0), "5 - 5 borrows nothing");
assert_eq!(add(3, !5, 1), (0xFFFF_FFFE, 0, 0), "3 - 5 borrows");
assert_eq!(add(0x8000_0000, !1, 1), (0x7FFF_FFFF, 1, 1));
assert_eq!(add(5, !5, 0), (0xFFFF_FFFF, 0, 0), "SBC takes one more");
}
#[test]
fn decode_imm_shift_reads_a_zero_immediate_as_the_manual_says() {
assert_eq!(decode_imm_shift(0b00, 0), (ShiftType::Lsl, 0));
assert_eq!(decode_imm_shift(0b00, 31), (ShiftType::Lsl, 31));
assert_eq!(decode_imm_shift(0b01, 0), (ShiftType::Lsr, 32));
assert_eq!(decode_imm_shift(0b01, 7), (ShiftType::Lsr, 7));
assert_eq!(decode_imm_shift(0b10, 0), (ShiftType::Asr, 32));
assert_eq!(decode_imm_shift(0b10, 31), (ShiftType::Asr, 31));
assert_eq!(decode_imm_shift(0b11, 0), (ShiftType::Rrx, 1));
assert_eq!(decode_imm_shift(0b11, 1), (ShiftType::Ror, 1));
}
#[test]
fn a_constant_shift_gives_the_result_and_carry_the_pseudocode_does() {
use ShiftType::{Asr, Lsl, Lsr, Ror, Rrx};
assert_eq!(shift(0x1234_5678, Lsl, 0, 1), (0x1234_5678, 1), "keeps C");
assert_eq!(shift(0x1234_5678, Lsl, 0, 0), (0x1234_5678, 0));
assert_eq!(shift(0x8000_0001, Lsl, 1, 0), (2, 1));
assert_eq!(shift(3, Lsl, 31, 0), (0x8000_0000, 1));
assert_eq!(shift(1, Lsl, 31, 1), (0x8000_0000, 0));
assert_eq!(shift(1, Lsl, 32, 0), (0, 1));
assert_eq!(shift(0xFFFF_FFFF, Lsl, 33, 1), (0, 0));
assert_eq!(shift(0x8000_0001, Lsr, 1, 0), (0x4000_0000, 1));
assert_eq!(shift(0x8000_0000, Lsr, 31, 0), (1, 0));
assert_eq!(shift(0x8000_0000, Lsr, 32, 0), (0, 1));
assert_eq!(shift(0xFFFF_FFFF, Lsr, 33, 1), (0, 0));
assert_eq!(shift(0x8000_0008, Asr, 4, 0), (0xF800_0000, 1));
assert_eq!(shift(0x4000_0000, Asr, 30, 1), (1, 0));
assert_eq!(shift(0x8000_0000, Asr, 32, 0), (0xFFFF_FFFF, 1));
assert_eq!(shift(0x7FFF_FFFF, Asr, 32, 1), (0, 0));
assert_eq!(shift(0x8000_0000, Asr, 40, 0), (0xFFFF_FFFF, 1));
assert_eq!(shift(0x12, Ror, 4, 1), (0x2000_0001, 0));
assert_eq!(shift(1, Ror, 1, 0), (0x8000_0000, 1));
assert_eq!(shift(0x8000_0000, Ror, 32, 0), (0x8000_0000, 1), "MOD 32");
assert_eq!(shift(3, Rrx, 1, 1), (0x8000_0001, 1));
assert_eq!(shift(3, Rrx, 1, 0), (1, 1));
assert_eq!(shift(2, Rrx, 1, 0), (1, 0));
}
#[test]
fn a_register_shift_gives_the_result_and_carry_the_pseudocode_does() {
use ShiftType::{Asr, Lsl, Lsr, Ror};
for shift_type in [Lsl, Lsr, Asr, Ror] {
assert_eq!(
shift_register(0x8000_0001, shift_type, 0, 1),
(0x8000_0001, 1),
"{shift_type:?} by 0 keeps C"
);
}
assert_eq!(shift_register(0x8000_0001, Lsl, 1, 0), (2, 1));
assert_eq!(shift_register(3, Lsl, 31, 0), (0x8000_0000, 1));
assert_eq!(shift_register(1, Lsl, 32, 0), (0, 1));
assert_eq!(shift_register(0xFFFF_FFFF, Lsl, 33, 1), (0, 0));
assert_eq!(shift_register(0xFFFF_FFFF, Lsl, 255, 1), (0, 0));
assert_eq!(shift_register(0x8000_0001, Lsr, 1, 0), (0x4000_0000, 1));
assert_eq!(shift_register(0x8000_0000, Lsr, 32, 0), (0, 1));
assert_eq!(shift_register(0xFFFF_FFFF, Lsr, 33, 1), (0, 0));
assert_eq!(shift_register(0xFFFF_FFFF, Lsr, 200, 1), (0, 0));
assert_eq!(shift_register(0x8000_0008, Asr, 4, 0), (0xF800_0000, 1));
assert_eq!(shift_register(0x8000_0000, Asr, 32, 0), (0xFFFF_FFFF, 1));
assert_eq!(shift_register(0x8000_0000, Asr, 255, 0), (0xFFFF_FFFF, 1));
assert_eq!(shift_register(0x7FFF_FFFF, Asr, 255, 1), (0, 0));
assert_eq!(shift_register(0x12, Ror, 4, 1), (0x2000_0001, 0));
assert_eq!(shift_register(0x8000_0000, Ror, 32, 0), (0x8000_0000, 1));
assert_eq!(shift_register(1, Ror, 33, 0), (0x8000_0000, 1));
assert_eq!(shift_register(0x7FFF_FFFF, Ror, 64, 1), (0x7FFF_FFFF, 0));
assert_eq!(shift_register(0x12, Ror, 0xFFFF_FFE4, 1), (0x2000_0001, 0));
assert_eq!(shift_register(1, Lsl, 0xFFFF_FFFF, 1), (0, 0));
}
#[test]
fn a_register_shift_agrees_with_a_constant_one() {
for shift_type in [
ShiftType::Lsl,
ShiftType::Lsr,
ShiftType::Asr,
ShiftType::Ror,
] {
for amount in 0..=40 {
for value in [0x8000_0001, 0x7FFF_FFFE, 0xA5A5_5A5A] {
for carry_in in [0, 1] {
assert_eq!(
shift_register(value, shift_type, amount, carry_in),
shift(value, shift_type, amount, carry_in),
"{shift_type:?} {value:#x} by {amount}, C {carry_in}"
);
}
}
}
}
}
#[test]
#[should_panic(expected = "Shift_C only shifts RRX by one")]
fn rrx_by_more_than_one_is_refused() {
shift(1, ShiftType::Rrx, 2, 0);
}
#[test]
fn thumb_expand_imm_gives_the_table_s_constants_and_carry() {
assert_eq!(thumb_expand_imm_c(0x0AB), (0x0000_00AB, None));
assert_eq!(thumb_expand_imm_c(0x1AB), (0x00AB_00AB, None));
assert_eq!(thumb_expand_imm_c(0x2AB), (0xAB00_AB00, None));
assert_eq!(thumb_expand_imm_c(0x3AB), (0xABAB_ABAB, None));
assert_eq!(thumb_expand_imm_c(0x455), (0xD500_0000, Some(1)), "01000");
assert_eq!(thumb_expand_imm_c(0x480), (0x4000_0000, Some(0)), "01001");
assert_eq!(thumb_expand_imm_c(0x500), (0x2000_0000, Some(0)), "01010");
assert_eq!(thumb_expand_imm_c(0xF7F), (0x0000_03FC, Some(0)), "11110");
assert_eq!(thumb_expand_imm_c(0xFFF), (0x0000_01FE, Some(0)), "11111");
}
#[test]
fn thumb_expand_imm_replicates_a_zero_byte_to_zero() {
for imm12 in [0x100, 0x200, 0x300] {
assert_eq!(thumb_expand_imm_c(imm12), (0, None));
}
}
#[test]
fn signed_sat_q_holds_to_the_range_and_says_when() {
assert_eq!(signed(127, 8), (0x7F, 0));
assert_eq!(signed(128, 8), (0x7F, 1));
assert_eq!(signed(-128, 8), (0x80, 0));
assert_eq!(signed(-129, 8), (0x80, 1));
assert_eq!(signed(0, 1), (0, 0));
assert_eq!(signed(-1, 1), (1, 0));
assert_eq!(signed(1, 1), (0, 1));
assert_eq!(signed(-2, 1), (1, 1));
assert_eq!(signed(0x7FFF_FFFF, 32), (0x7FFF_FFFF, 0));
assert_eq!(signed(0x8000_0000, 32), (0x7FFF_FFFF, 1));
assert_eq!(signed(-0x8000_0000, 32), (0x8000_0000, 0));
assert_eq!(signed(-0x8000_0001, 32), (0x8000_0000, 1));
assert_eq!(signed(-0x1_0000_0000, 32), (0x8000_0000, 1));
assert_eq!(signed(-5, 16), (0xFFFB, 0));
}
#[test]
fn unsigned_sat_q_holds_to_the_range_and_says_when() {
assert_eq!(unsigned(255, 8), (0xFF, 0));
assert_eq!(unsigned(256, 8), (0xFF, 1));
assert_eq!(unsigned(0, 8), (0, 0));
assert_eq!(unsigned(-1, 8), (0, 1));
assert_eq!(unsigned(0, 0), (0, 0));
assert_eq!(unsigned(5, 0), (0, 1));
assert_eq!(unsigned(-1, 0), (0, 1));
assert_eq!(unsigned(0x7FFF_FFFF, 31), (0x7FFF_FFFF, 0));
assert_eq!(unsigned(0x8000_0000, 31), (0x7FFF_FFFF, 1));
assert_eq!(unsigned(-0x8000_0000, 31), (0, 1));
assert_eq!(unsigned(0x1_FFFF, 16), (0xFFFF, 1));
}
}