use super::*;
#[derive(Clone, Copy)]
struct ContiguousLoadPack {
base: BaseReg,
memory_bytes: [Option<i64>; 8],
earliest_instruction: usize,
}
impl ContiguousLoadPack {
fn from_load(base: BaseReg, offset: i32, size: OpSize, instruction: usize) -> Self {
let mut memory_bytes = [None; 8];
for (byte, slot) in memory_bytes
.iter_mut()
.enumerate()
.take(size.bytes() as usize)
{
*slot = Some(i64::from(offset) + byte as i64);
}
Self {
base,
memory_bytes,
earliest_instruction: instruction,
}
}
fn shifted_left(self, bits: u8) -> Option<Self> {
if !bits.is_multiple_of(8) || bits >= 64 {
return None;
}
let bytes = usize::from(bits / 8);
if self.memory_bytes[8 - bytes..].iter().any(Option::is_some) {
return None;
}
let mut memory_bytes = [None; 8];
memory_bytes[bytes..].copy_from_slice(&self.memory_bytes[..8 - bytes]);
Some(Self {
memory_bytes,
..self
})
}
fn merged(self, other: Self) -> Option<Self> {
if self.base != other.base {
return None;
}
let mut memory_bytes = self.memory_bytes;
for (destination, source) in memory_bytes.iter_mut().zip(other.memory_bytes) {
if destination.is_some() && source.is_some() {
return None;
}
if destination.is_none() {
*destination = source;
}
}
Some(Self {
base: self.base,
memory_bytes,
earliest_instruction: self.earliest_instruction.min(other.earliest_instruction),
})
}
fn native_load(self) -> Option<(i32, OpSize)> {
for (byte_len, size) in [
(8usize, OpSize::S64),
(4usize, OpSize::S32),
(2usize, OpSize::S16),
] {
if self.memory_bytes[byte_len..].iter().any(Option::is_some) {
continue;
}
let start = self.memory_bytes[0]?;
if self.memory_bytes[..byte_len]
.iter()
.enumerate()
.all(|(byte, address)| *address == Some(start + byte as i64))
{
return Some((i32::try_from(start).ok()?, size));
}
}
None
}
}
pub(super) fn fold_contiguous_load_packs(func: &mut MFunction) {
for block in &mut func.blocks {
let mut summaries = HashMap::<VReg, ContiguousLoadPack>::default();
let mut last_write = None::<usize>;
for instruction_index in 0..block.insts.len() {
let original = block.insts[instruction_index].clone();
let summary = match original {
MInst::Load {
base, offset, size, ..
} => Some(ContiguousLoadPack::from_load(
base,
offset,
size,
instruction_index,
)),
MInst::ShlImm { src, imm, .. } => summaries
.get(&src)
.copied()
.and_then(|summary| summary.shifted_left(imm)),
MInst::Or { lhs, rhs, .. } => summaries
.get(&lhs)
.copied()
.zip(summaries.get(&rhs).copied())
.and_then(|(lhs, rhs)| lhs.merged(rhs)),
MInst::Mov { src, .. } => summaries.get(&src).copied(),
MInst::Mov32 { src, .. } => summaries
.get(&src)
.copied()
.filter(|summary| summary.memory_bytes[4..].iter().all(Option::is_none)),
MInst::OrImm { src, imm: 0, .. } => summaries.get(&src).copied(),
_ => None,
};
if let (Some(dst), Some(summary)) = (original.def(), summary) {
if matches!(original, MInst::Or { .. })
&& last_write.is_none_or(|write| write < summary.earliest_instruction)
&& let Some((offset, size)) = summary.native_load()
{
block.insts[instruction_index] = MInst::Load {
dst,
base: summary.base,
offset,
size,
};
}
summaries.insert(dst, summary);
}
if memory_effect::writes(&original).has_effect() {
last_write = Some(instruction_index);
}
}
}
}
pub(super) fn fold_contiguous_memory_copies(func: &mut MFunction) {
let mut use_counts = HashMap::<VReg, usize>::default();
for block in &func.blocks {
for phi in &block.phis {
for (_, source) in &phi.sources {
*use_counts.entry(*source).or_default() += 1;
}
}
for inst in &block.insts {
for source in inst.uses() {
*use_counts.entry(source).or_default() += 1;
}
}
}
for block in &mut func.blocks {
let original = std::mem::take(&mut block.insts);
let mut rewritten = Vec::with_capacity(original.len());
let mut cursor = 0usize;
while cursor < original.len() {
let Some((src_offset, dst_offset, size)) =
direct_copy_pair(&original, cursor, &use_counts)
else {
rewritten.push(original[cursor].clone());
cursor += 1;
continue;
};
let chunk_bytes = size.bytes() as usize;
let mut pairs = 1usize;
while let Some((next_src, next_dst, next_size)) =
direct_copy_pair(&original, cursor + pairs * 2, &use_counts)
{
let Some(delta) = pairs
.checked_mul(chunk_bytes)
.and_then(|delta| i32::try_from(delta).ok())
else {
break;
};
if next_size != size
|| src_offset.checked_add(delta) != Some(next_src)
|| dst_offset.checked_add(delta) != Some(next_dst)
{
break;
}
pairs += 1;
}
let byte_len = pairs * chunk_bytes;
let src_end = i64::from(src_offset) + byte_len as i64;
let dst_end = i64::from(dst_offset) + byte_len as i64;
let disjoint = src_end <= i64::from(dst_offset) || dst_end <= i64::from(src_offset);
if byte_len >= 16 && disjoint {
rewritten.push(MInst::MemCopy {
src_offset,
dst_offset,
byte_len,
});
cursor += pairs * 2;
} else {
rewritten.push(original[cursor].clone());
cursor += 1;
}
}
block.insts = rewritten;
}
}
fn direct_copy_pair(
instructions: &[MInst],
index: usize,
use_counts: &HashMap<VReg, usize>,
) -> Option<(i32, i32, OpSize)> {
let [
MInst::Load {
dst,
base: BaseReg::SimState,
offset: src_offset,
size,
},
MInst::Store {
base: BaseReg::SimState,
offset: dst_offset,
src,
size: store_size,
},
] = instructions.get(index..index.checked_add(2)?)?
else {
return None;
};
(*src == *dst && *store_size == *size && use_counts.get(dst).copied() == Some(1)).then_some((
*src_offset,
*dst_offset,
*size,
))
}