#![deny(clippy::arithmetic_side_effects)]
use super::Overflow;
pub const INSN_SIZE: u64 = 4;
pub const NOP: u32 = 0x0340_0000;
pub const BREAK: u32 = 0x002a_0000;
pub const PCADDI: u32 = 0x1800_0000;
pub const PCALAU12I: u32 = 0x1a00_0000;
pub const PCADDU12I: u32 = 0x1c00_0000;
pub const PCADDU18I: u32 = 0x1e00_0000;
pub const LU12I_W: u32 = 0x1400_0000;
pub const ADDI_W: u32 = 0x0280_0000;
pub const ADDI_D: u32 = 0x02c0_0000;
pub const ORI: u32 = 0x0380_0000;
pub const LD_W: u32 = 0x2880_0000;
pub const LD_D: u32 = 0x28c0_0000;
pub const ADD_D: u32 = 0x0010_8000;
pub const SUB_D: u32 = 0x0011_8000;
pub const SRLI_D: u32 = 0x0045_0000;
pub const JIRL: u32 = 0x4c00_0000;
pub const B: u32 = 0x5000_0000;
pub const BL: u32 = 0x5400_0000;
pub const R_ZERO: u32 = 0;
pub const R_RA: u32 = 1;
pub const R_TP: u32 = 2;
pub const R_A0: u32 = 4;
pub const R_T0: u32 = 12;
pub const R_T1: u32 = 13;
pub const R_T2: u32 = 14;
pub const R_T3: u32 = 15;
pub const B26_REACH: i64 = 1 << 27;
pub const PCADDI_REACH: i64 = 1 << 21;
#[must_use]
pub const fn page(address: u64) -> u64 {
address & !0xfff
}
#[must_use]
pub fn page_delta(dest: u64, pc: u64) -> u64 {
let mut result = page(dest).wrapping_sub(page(pc));
if dest & 0x800 != 0 {
result = result.wrapping_add(0x1000).wrapping_sub(0x1_0000_0000);
}
if result & 0x8000_0000 != 0 {
result = result.wrapping_add(0x1_0000_0000);
}
result
}
#[must_use]
pub const fn pcrel_hi20(offset: u32) -> u32 {
offset.wrapping_add(0x800) >> 12
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Field {
Hi20,
Hi20Round,
Lo20,
Lo12,
Hi12,
B16,
B21,
B26,
Pcrel20S2,
JirlLo12,
Call36,
Call36Relax,
TpHi20Relax,
TpAddRelax,
TpLo12Relax,
Data6,
Uleb128,
}
#[must_use]
pub fn read_insn(data: &[u8], at: usize) -> Option<u32> {
data.get(at..)
.and_then(|rest| rest.first_chunk::<4>())
.map(|word| u32::from_le_bytes(*word))
}
pub fn write_insn(data: &mut [u8], at: usize, insn: u32) -> Option<()> {
let slot = data
.get_mut(at..)
.and_then(|rest| rest.first_chunk_mut::<4>())?;
*slot = insn.to_le_bytes();
Some(())
}
#[must_use]
pub fn fits_signed(value: i64, bits: u32) -> bool {
if bits >= 64 {
return true;
}
let Some(limit) = 1i64.checked_shl(bits.wrapping_sub(1)) else {
return true;
};
value >= limit.wrapping_neg() && value < limit
}
#[must_use]
pub fn fits_unsigned(value: i64, bits: u32) -> bool {
if bits >= 64 {
return true;
}
let Some(limit) = 1i64.checked_shl(bits) else {
return true;
};
value >= 0 && value < limit
}
fn bits_of(value: u64, lsb: u32, bits: u32) -> u32 {
let mask = 1u64
.checked_shl(bits)
.map_or(u64::MAX, |b| b.wrapping_sub(1));
(value.checked_shr(lsb).unwrap_or(0) & mask) as u32
}
fn insert(insn: u32, value: u32, lsb: u32, bits: u32) -> u32 {
let mask = match 1u32.checked_shl(bits) {
Some(bit) => bit.wrapping_sub(1),
None => u32::MAX,
};
let shifted_mask = mask.checked_shl(lsb).unwrap_or(0);
(insn & !shifted_mask) | ((value & mask).checked_shl(lsb).unwrap_or(0))
}
#[must_use]
pub const fn rd(insn: u32) -> u32 {
insn & 0x1f
}
#[must_use]
pub const fn rj(insn: u32) -> u32 {
(insn >> 5) & 0x1f
}
#[must_use]
pub const fn with_rj(insn: u32, rj: u32) -> u32 {
(insn & !(0x1f << 5)) | ((rj & 0x1f) << 5)
}
#[must_use]
pub const fn is_pcalau12i(insn: u32) -> bool {
insn & 0xfe00_0000 == PCALAU12I
}
#[must_use]
pub const fn is_jirl(insn: u32) -> bool {
insn & 0xfc00_0000 == JIRL
}
#[must_use]
pub const fn is_ld_word(insn: u32) -> bool {
let op = insn & 0xffc0_0000;
op == LD_D || op == LD_W
}
#[must_use]
pub const fn is_addi(insn: u32) -> bool {
let op = insn & 0xffc0_0000;
op == ADDI_D || op == ADDI_W
}
#[must_use]
pub const fn ri20(op: u32, rd: u32, si20: u32) -> u32 {
op | (rd & 0x1f) | ((si20 & 0xf_ffff) << 5)
}
#[must_use]
pub const fn rri12(op: u32, rd: u32, rj: u32, imm12: u32) -> u32 {
op | (rd & 0x1f) | ((rj & 0x1f) << 5) | ((imm12 & 0xfff) << 10)
}
#[must_use]
pub const fn rrr(op: u32, rd: u32, rj: u32, rk: u32) -> u32 {
op | (rd & 0x1f) | ((rj & 0x1f) << 5) | ((rk & 0x1f) << 10)
}
#[must_use]
pub const fn jirl(rd: u32, rj: u32) -> u32 {
JIRL | (rd & 0x1f) | ((rj & 0x1f) << 5)
}
impl Field {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
Self::Call36 | Self::Call36Relax => 8,
Self::Data6 | Self::Uleb128 => 1,
_ => 4,
}
}
#[must_use]
pub const fn is_data(self) -> bool {
matches!(self, Self::Data6 | Self::Uleb128)
}
pub fn encode(self, insn: u32, value: i64) -> Result<u32, Overflow> {
let raw = value as u64;
let branch = |bits: u32| {
if value & 3 != 0 || !fits_signed(value, bits) {
Err(Overflow)
} else {
Ok((value >> 2) as u32)
}
};
match self {
Self::Hi20 => Ok(insert(insn, bits_of(raw, 12, 20), 5, 20)),
Self::Hi20Round => Ok(insert(
insn,
bits_of(raw.wrapping_add(0x800), 12, 20),
5,
20,
)),
Self::Lo20 => Ok(insert(insn, bits_of(raw, 32, 20), 5, 20)),
Self::Lo12 => Ok(insert(insn, bits_of(raw, 0, 12), 10, 12)),
Self::Hi12 => Ok(insert(insn, bits_of(raw, 52, 12), 10, 12)),
Self::B16 => Ok(insert(insn, branch(18)?, 10, 16)),
Self::B21 => {
let offs = branch(23)?;
Ok(insert(insert(insn, offs, 10, 16), offs >> 16, 0, 5))
}
Self::B26 => {
let offs = branch(28)?;
Ok(insert(insert(insn, offs, 10, 16), offs >> 16, 0, 10))
}
Self::Pcrel20S2 => Ok(insert(insn, branch(22)?, 5, 20)),
Self::JirlLo12 => {
if value & 3 != 0 {
return Err(Overflow);
}
let low = ((raw << 52) as i64) >> 52;
Ok(insert(insn, (low >> 2) as u32, 10, 16))
}
Self::Call36
| Self::Call36Relax
| Self::TpHi20Relax
| Self::TpAddRelax
| Self::TpLo12Relax
| Self::Data6
| Self::Uleb128 => Err(Overflow),
}
}
}
pub fn call36(pcaddu18i: u32, jirl: u32, value: i64) -> Result<[u32; 2], Overflow> {
let adjusted = value.checked_add(0x2_0000).ok_or(Overflow)?;
if value & 3 != 0 || !fits_signed(adjusted, 38) {
return Err(Overflow);
}
let hi20 = bits_of(adjusted as u64, 18, 20);
let lo16 = bits_of(value as u64, 2, 16);
Ok([insert(pcaddu18i, hi20, 5, 20), insert(jirl, lo16, 10, 16)])
}
#[must_use]
pub fn read_uleb128(data: &[u8]) -> Option<(u64, usize)> {
let mut value = 0u64;
for (index, byte) in data.iter().enumerate().take(10) {
let shift = u32::try_from(index).ok()?.checked_mul(7)?;
value |= u64::from(byte & 0x7f).checked_shl(shift).unwrap_or(0);
if byte & 0x80 == 0 {
return Some((value, index.checked_add(1)?));
}
}
None
}
pub fn write_uleb128(data: &mut [u8], value: u64) {
let count = data.len();
let mut rest = value;
for (index, byte) in data.iter_mut().enumerate() {
let more = if index.checked_add(1) == Some(count) {
0
} else {
0x80
};
*byte = (rest & 0x7f) as u8 | more;
rest >>= 7;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simulate(pc: u64, dest: u64) -> (u64, u64) {
let delta = page_delta(dest, pc);
let hi20 = (delta >> 12) & 0xf_ffff;
let t0 = page(pc).wrapping_add((((hi20 << 44) as i64) >> 32) as u64);
let lo12 = (((dest << 52) as i64) >> 52) as u64;
let normal = t0.wrapping_add(lo12);
let lo20 = (delta >> 32) & 0xf_ffff;
let hi12 = delta >> 52;
let mut t1 = lo12 & 0xffff_ffff;
t1 |= ((((lo20 << 44) as i64) >> 12) as u64) & !0xffff_ffff;
t1 = (t1 & ((1 << 52) - 1)) | (hi12 << 52);
(normal, t0.wrapping_add(t1))
}
#[test]
fn page_delta_rounds_for_the_signed_low_part() {
assert_eq!(page_delta(0x1234, 0x1000), 0);
assert_eq!(page_delta(0x1800, 0x1000) & 0xffff_ffff, 0x1000);
assert_eq!(page_delta(0x1000, 0x5000) as i64 as i32, -0x4000);
for (pc, dest) in [
(0x1000, 0x1800),
(0x1000, 0x17ff),
(0x1_2000_0000, 0x1_2000_0800),
(0x1_2000_4000, 0x1_2000_0010),
(0x1_2000_4000, 0x1_2000_0ff0),
(0, 0x7fff_f7fc),
] {
assert_eq!(simulate(pc, dest), (dest, dest), "{pc:#x} -> {dest:#x}");
}
for (pc, dest) in [
(0, 0x7fff_f800),
(0x1000, 0x12_3456_789a),
(0x7654_3210_0000, 0x1000_0800),
(0x1000, 0xffff_8000_0000_0800),
] {
assert_eq!(simulate(pc, dest).1, dest, "{pc:#x} -> {dest:#x}");
}
}
#[test]
fn immediates_are_placed() {
let insn = Field::Hi20
.encode(ri20(PCALAU12I, R_A0, 0), 0x1234_5000)
.unwrap();
assert_eq!(insn, 0x1a24_68a4);
let insn = Field::Lo12
.encode(rri12(ADDI_D, R_A0, R_A0, 0), 0x1234_5678)
.unwrap();
assert_eq!(insn, 0x02d9_e084);
assert_eq!(
Field::Lo20
.encode(0x1600_0004, 0x000a_bcde_0000_0000)
.unwrap()
>> 5,
(0x1600_0004 >> 5) | 0xabcde
);
assert_eq!(
Field::Hi12
.encode(0x0300_0084, 0x7ff0_0000_0000_0000u64 as i64)
.unwrap()
>> 10
& 0xfff,
0x7ff
);
}
#[test]
fn branches_are_checked_and_split() {
assert_eq!(Field::B26.encode(BL, 8).unwrap(), 0x5400_0800);
assert_eq!(Field::B26.encode(BL, -4).unwrap(), 0x57ff_ffff);
assert_eq!(Field::B26.encode(BL, 2), Err(Overflow));
assert_eq!(Field::B26.encode(BL, B26_REACH), Err(Overflow));
assert!(Field::B26.encode(BL, B26_REACH - 4).is_ok());
let beqz = 0x4000_0080;
let encoded = Field::B21.encode(beqz, 0xf_fffc).unwrap();
assert_eq!((encoded >> 10) & 0xffff, 0xffff);
assert_eq!(encoded & 0x1f, 0x3);
assert_eq!(Field::B21.encode(beqz, 1 << 22), Err(Overflow));
assert_eq!(Field::B16.encode(0x5800_0000, 1 << 17), Err(Overflow));
assert_eq!(
Field::Pcrel20S2.encode(PCADDI, 8).unwrap(),
PCADDI | (2 << 5)
);
}
#[test]
fn call36_compensates_for_the_signed_jirl_offset() {
let words = call36(ri20(PCADDU18I, R_RA, 0), jirl(R_RA, R_RA), 0x2_0000).unwrap();
assert_eq!((words[0] >> 5) & 0xf_ffff, 1);
assert_eq!((words[1] >> 10) & 0xffff, 0x8000);
assert_eq!(call36(PCADDU18I, JIRL, 2), Err(Overflow));
assert_eq!(call36(PCADDU18I, JIRL, 1 << 40), Err(Overflow));
}
#[test]
fn uleb128_keeps_its_length() {
let mut data = [0x80, 0x00];
assert_eq!(read_uleb128(&data), Some((0, 2)));
write_uleb128(&mut data, 0x81);
assert_eq!(data, [0x81, 0x01]);
assert_eq!(read_uleb128(&data), Some((0x81, 2)));
assert_eq!(read_uleb128(&[0x80]), None);
}
}