#![deny(clippy::arithmetic_side_effects)]
use crate::arch::loongarch::{self as insn, B, BL, Field, PCADDI, R_RA, R_ZERO};
use crate::elf::arch::shrink::{Edits, Pass, Rewrite, SectionInput};
use crate::elf::arch::{GotKind, Kind};
use crate::elf::read::Relocation;
use crate::error::Result;
use super::{
R_LARCH_ALIGN, R_LARCH_B26, R_LARCH_CALL36, R_LARCH_GOT_PC_HI20, R_LARCH_PCALA_HI20,
R_LARCH_PCREL20_S2, R_LARCH_RELAX, R_LARCH_TLS_DESC_LD, R_LARCH_TLS_DESC_PC_HI20,
R_LARCH_TLS_DESC_PC_LO12, R_LARCH_TLS_DESC_PCREL20_S2, R_LARCH_TLS_GD_PC_HI20,
R_LARCH_TLS_GD_PCREL20_S2, R_LARCH_TLS_IE_PC_HI20, R_LARCH_TLS_LD_PC_HI20,
R_LARCH_TLS_LE_ADD_R, R_LARCH_TLS_LE_HI20_R, R_LARCH_TLS_LE_LO12_R, RELAX_HINT,
};
fn relaxable(relocs: &[Relocation], index: usize) -> bool {
let Some(rel) = relocs.get(index) else {
return false;
};
relocs
.get(index.saturating_add(1))
.is_some_and(|next| next.r_type == R_LARCH_RELAX && next.offset == rel.offset)
}
fn pair_relaxable(relocs: &[Relocation], index: usize) -> bool {
let (Some(hi), Some(lo)) = (relocs.get(index), relocs.get(index.saturating_add(2))) else {
return false;
};
relaxable(relocs, index)
&& relaxable(relocs, index.saturating_add(2))
&& hi.offset.checked_add(4) == Some(lo.offset)
}
fn pcaddi_reaches(displace: i64) -> bool {
displace & 3 == 0 && insn::fits_signed(displace, 22)
}
#[allow(clippy::too_many_lines)]
pub fn decide<F: crate::elf::read::ElfFormat>(
pass: &Pass<'_, '_, '_, F>,
section: &SectionInput<'_, '_, F>,
) -> Result<Edits> {
let mut edits = Edits::default();
let relocs = §ion.relocs;
let mut pair: Option<(u32, u32, u32)> = None;
for (seq, rel) in (0u32..).zip(relocs) {
let index = seq as usize;
let loc = section
.address
.wrapping_add(rel.offset)
.wrapping_sub(edits.delta());
let mut remove = 0u32;
let mut rewrite = None;
if let Some((at, word, r_type)) = pair
&& at == seq
{
pair = None;
edits.push(
section,
seq,
rel.offset,
0,
Rewrite::Replace {
word,
len: 4,
r_type,
},
);
continue;
}
let kind = |r_type: u32| {
let context = pass.classify(section.file, rel.symbol);
super::classify(r_type, rel.addend, section.data, rel.offset, context)
.map_or(Kind::None, |class| class.kind)
};
let tpoff = || {
pass.tp
.and_then(|tp| section.target(pass, seq, false).map(|s| s.wrapping_sub(tp)))
.map(|value| value as i64)
};
match rel.r_type {
R_LARCH_ALIGN => {
let Some((all_bytes, trim)) = align_trim(rel.symbol, rel.addend, loc) else {
continue;
};
let Some(trim) = trim else {
return Err(section.malformed(
rel.offset,
format!(
"insufficient padding bytes for R_LARCH_ALIGN: {all_bytes} bytes available"
),
));
};
if trim != 0 {
remove = u32::try_from(trim).unwrap_or(u32::MAX);
rewrite = Some(Rewrite::Align {
addend: u32::try_from(all_bytes).unwrap_or(u32::MAX),
});
}
}
R_LARCH_PCALA_HI20 | R_LARCH_GOT_PC_HI20
if pass.relax
&& pair_relaxable(relocs, index)
&& kind(rel.r_type | RELAX_HINT) == Kind::Relax =>
{
if let Some((word, at)) = fold_pair(pass, section, seq, loc, None) {
remove = 4;
rewrite = Some(Rewrite::Delete);
pair = Some((at, word, R_LARCH_PCREL20_S2));
}
}
R_LARCH_TLS_GD_PC_HI20 | R_LARCH_TLS_LD_PC_HI20
if pass.relax && pair_relaxable(relocs, index) =>
{
let slot = pass.got_address(section.file, rel.symbol, GotKind::TlsGd);
if let Some((word, at)) = fold_pair(pass, section, seq, loc, slot) {
remove = 4;
rewrite = Some(Rewrite::Delete);
pair = Some((at, word, R_LARCH_TLS_GD_PCREL20_S2));
}
}
R_LARCH_TLS_DESC_PC_HI20 => match kind(rel.r_type) {
Kind::DescToLe | Kind::DescToIe if pass.relax && relaxable(relocs, index) => {
remove = 4;
rewrite = Some(Rewrite::Delete);
}
_ if pass.relax && pair_relaxable(relocs, index) => {
let slot = pass.got_address(section.file, rel.symbol, GotKind::TlsDesc);
if let Some((word, at)) = fold_pair(pass, section, seq, loc, slot) {
remove = 4;
rewrite = Some(Rewrite::Delete);
pair = Some((at, word, R_LARCH_TLS_DESC_PCREL20_S2));
}
}
_ => {}
},
R_LARCH_TLS_DESC_PC_LO12
if pass.relax
&& relaxable(relocs, index)
&& matches!(kind(rel.r_type), Kind::DescToLe | Kind::DescToIe) =>
{
remove = 4;
rewrite = Some(Rewrite::Delete);
}
R_LARCH_TLS_DESC_LD
if pass.relax
&& relaxable(relocs, index)
&& kind(rel.r_type) == Kind::DescToLe
&& tpoff().is_some_and(|value| insn::fits_unsigned(value, 12)) =>
{
remove = 4;
rewrite = Some(Rewrite::Delete);
}
R_LARCH_TLS_IE_PC_HI20
if pass.relax
&& relaxable(relocs, index)
&& kind(rel.r_type) == Kind::IeToLe
&& tpoff().is_some_and(|value| insn::fits_unsigned(value, 12)) =>
{
remove = 4;
rewrite = Some(Rewrite::Delete);
}
R_LARCH_TLS_LE_HI20_R | R_LARCH_TLS_LE_ADD_R
if pass.relax
&& relaxable(relocs, index)
&& tpoff().is_some_and(|value| insn::fits_signed(value, 12)) =>
{
remove = 4;
rewrite = Some(Rewrite::Delete);
}
R_LARCH_TLS_LE_LO12_R
if pass.relax
&& relaxable(relocs, index)
&& tpoff().is_some_and(|value| insn::fits_signed(value, 12)) =>
{
let word = insn::read_insn(section.data, index_of(rel.offset))
.map(|word| insn::with_rj(word, insn::R_TP));
if let Some(word) = word {
rewrite = Some(Rewrite::Replace {
word,
len: 4,
r_type: R_LARCH_TLS_LE_LO12_R,
});
}
}
R_LARCH_CALL36 if pass.relax && relaxable(relocs, index) => {
let link = rel
.offset
.checked_add(4)
.and_then(|at| insn::read_insn(section.data, index_of(at)))
.map(insn::rd);
let op = match link {
Some(R_RA) => BL,
Some(R_ZERO) => B,
_ => continue,
};
if let Some(dest) = section.target(pass, seq, true) {
let displace = dest.wrapping_sub(loc) as i64;
if Field::B26.encode(0, displace).is_ok() {
remove = 4;
rewrite = Some(Rewrite::Replace {
word: op,
len: 4,
r_type: R_LARCH_B26,
});
}
}
}
_ => {}
}
if let Some(rewrite) = rewrite {
edits.push(section, seq, rel.offset, remove, rewrite);
}
}
Ok(edits)
}
fn index_of(offset: u64) -> usize {
usize::try_from(offset).unwrap_or(usize::MAX)
}
fn fold_pair<F: crate::elf::read::ElfFormat>(
pass: &Pass<'_, '_, '_, F>,
section: &SectionInput<'_, '_, F>,
seq: u32,
loc: u64,
slot: Option<u64>,
) -> Option<(u32, u32)> {
let at = seq.checked_add(2)?;
section.relocs.get(at as usize)?;
let dest = match slot {
Some(slot) => slot,
None => section.target(pass, seq, false)?,
};
let displace = dest.wrapping_sub(loc) as i64;
if !pcaddi_reaches(displace) {
return None;
}
let offset = section.relocs.get(seq as usize)?.offset;
let register = super::pair_register(section.data, offset)?;
Some((insn::ri20(PCADDI, register, 0), at))
}
fn align_trim(symbol: u32, addend: i64, loc: u64) -> Option<(u64, Option<u64>)> {
let addend = u64::try_from(addend).ok()?;
let encoded = if symbol == 0 {
if addend == 0 {
return None;
}
u64::from(addend.ilog2()).checked_add(1)?
} else {
addend
};
let exponent = encoded & 0xff;
if !(3..64).contains(&exponent) {
return None;
}
let align = 1u64.checked_shl(u32::try_from(exponent).ok()?)?;
let all_bytes = align.checked_sub(4)?;
let max_bytes = encoded >> 8;
let offset = loc & align.wrapping_sub(1);
let needed = if offset == 0 {
0
} else {
align.wrapping_sub(offset)
};
if max_bytes != 0 && needed > max_bytes {
return Some((all_bytes, Some(all_bytes)));
}
Some((all_bytes, all_bytes.checked_sub(needed)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alignment_padding_is_trimmed_to_the_boundary() {
assert_eq!(align_trim(1, 4, 0x1000), Some((12, Some(12))));
assert_eq!(align_trim(1, 4, 0x1004), Some((12, Some(0))));
assert_eq!(align_trim(1, 4, 0x1008), Some((12, Some(4))));
assert_eq!(align_trim(1, 4 | (4 << 8), 0x1004), Some((12, Some(12))));
assert_eq!(align_trim(0, 12, 0x1008), Some((12, Some(4))));
assert_eq!(align_trim(0, 28, 0x1000), Some((28, Some(28))));
assert_eq!(align_trim(0, 0, 0x1000), None);
}
#[test]
fn pcaddi_reach_is_twenty_two_signed_bits() {
assert!(pcaddi_reaches(0));
assert!(pcaddi_reaches((1 << 21) - 4));
assert!(!pcaddi_reaches(1 << 21));
assert!(pcaddi_reaches(-(1 << 21)));
assert!(!pcaddi_reaches(2));
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SectionAlign {
pub relaxes: bool,
pub covered: bool,
}
#[must_use]
pub fn section_align<F: crate::elf::read::ElfFormat>(
relocs: &crate::elf::read::RelaSlice<'_, F>,
align: u64,
) -> SectionAlign {
let mut out = SectionAlign::default();
for rel in relocs.iter() {
if rel.r_type == R_LARCH_RELAX {
out.relaxes = true;
} else if rel.r_type == R_LARCH_ALIGN
&& rel.offset == 0
&& u64::try_from(rel.addend).is_ok_and(|addend| addend >= align.saturating_sub(4))
{
out.covered = true;
}
}
out
}