use std::{
collections::{HashMap, VecDeque},
fmt,
};
use crate::{
ir::{
instruction::SsaInstruction,
ops::SsaOp,
phi::{PhiNode, PhiOperand},
variable::SsaVarId,
},
target::Target,
BitSet,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct ReplaceResult {
pub replaced: usize,
pub skipped: usize,
}
impl ReplaceResult {
#[must_use]
pub const fn is_complete(&self) -> bool {
self.skipped == 0
}
}
impl std::ops::Add for ReplaceResult {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
replaced: self.replaced.saturating_add(rhs.replaced),
skipped: self.skipped.saturating_add(rhs.skipped),
}
}
}
#[derive(Debug, Clone)]
pub struct SsaBlock<T: Target> {
id: usize,
phi_nodes: Vec<PhiNode>,
instructions: Vec<SsaInstruction<T>>,
}
impl<T: Target> SsaBlock<T> {
#[must_use]
pub fn new(id: usize) -> Self {
Self {
id,
phi_nodes: Vec::new(),
instructions: Vec::new(),
}
}
#[must_use]
pub fn with_capacity(id: usize, phi_capacity: usize, instr_capacity: usize) -> Self {
Self {
id,
phi_nodes: Vec::with_capacity(phi_capacity),
instructions: Vec::with_capacity(instr_capacity),
}
}
#[must_use]
pub const fn id(&self) -> usize {
self.id
}
pub fn set_id(&mut self, id: usize) {
self.id = id;
}
#[must_use]
pub fn phi_nodes(&self) -> &[PhiNode] {
&self.phi_nodes
}
pub fn phi_nodes_mut(&mut self) -> &mut Vec<PhiNode> {
&mut self.phi_nodes
}
#[must_use]
pub fn instructions(&self) -> &[SsaInstruction<T>] {
&self.instructions
}
pub fn instructions_mut(&mut self) -> &mut Vec<SsaInstruction<T>> {
&mut self.instructions
}
#[must_use]
pub fn phi_count(&self) -> usize {
self.phi_nodes.len()
}
#[must_use]
pub fn instruction_count(&self) -> usize {
self.instructions.len()
}
#[must_use]
pub fn has_no_phis(&self) -> bool {
self.phi_nodes.is_empty()
}
#[must_use]
pub fn has_no_instructions(&self) -> bool {
self.instructions.is_empty()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.phi_nodes.is_empty() && self.instructions.is_empty()
}
pub fn clear(&mut self) {
self.phi_nodes.clear();
self.instructions.clear();
}
pub fn add_phi(&mut self, phi: PhiNode) {
self.phi_nodes.push(phi);
}
pub fn add_instruction(&mut self, instr: SsaInstruction<T>) {
self.instructions.push(instr);
}
#[must_use]
pub fn phi(&self, index: usize) -> Option<&PhiNode> {
self.phi_nodes.get(index)
}
pub fn phi_mut(&mut self, index: usize) -> Option<&mut PhiNode> {
self.phi_nodes.get_mut(index)
}
#[must_use]
pub fn instruction(&self, index: usize) -> Option<&SsaInstruction<T>> {
self.instructions.get(index)
}
pub fn instruction_mut(&mut self, index: usize) -> Option<&mut SsaInstruction<T>> {
self.instructions.get_mut(index)
}
#[must_use]
pub fn terminator(&self) -> Option<&SsaInstruction<T>> {
self.instructions.last()
}
#[must_use]
pub fn terminator_op(&self) -> Option<&SsaOp<T>> {
self.instructions.last().map(SsaInstruction::op)
}
#[must_use]
pub fn successors(&self) -> Vec<usize> {
self.terminator_op()
.map_or_else(Vec::new, super::SsaOp::successors)
}
pub fn redirect_target(&mut self, old_target: usize, new_target: usize) -> bool {
if let Some(terminator) = self.instructions.last_mut() {
return terminator.op_mut().redirect_target(old_target, new_target);
}
false
}
pub fn set_target(&mut self, target: usize) {
if let Some(terminator) = self.instructions.last_mut() {
match terminator.op_mut() {
SsaOp::Jump { target: t } | SsaOp::Leave { target: t } => {
*t = target;
}
SsaOp::Branch {
true_target,
false_target,
..
}
| SsaOp::BranchCmp {
true_target,
false_target,
..
} => {
*true_target = target;
*false_target = target;
}
SsaOp::Switch {
targets, default, ..
} => {
*default = target;
for t in targets.iter_mut() {
*t = target;
}
}
_ => {
*terminator = SsaInstruction::synthetic(SsaOp::Jump { target });
}
}
} else {
self.instructions
.push(SsaInstruction::synthetic(SsaOp::Jump { target }));
}
}
pub fn replace_uses(&mut self, old_var: SsaVarId, new_var: SsaVarId) -> ReplaceResult {
let mut replaced: usize = 0;
let mut skipped: usize = 0;
for instr in &mut self.instructions {
let op = instr.op_mut();
if let Some(dest) = op.dest() {
if dest == new_var {
if op.uses().contains(&old_var) {
skipped = skipped.saturating_add(1);
}
continue;
}
}
replaced = replaced.saturating_add(op.replace_uses(old_var, new_var));
}
ReplaceResult { replaced, skipped }
}
pub fn replace_uses_including_phis(
&mut self,
old_var: SsaVarId,
new_var: SsaVarId,
) -> ReplaceResult {
let mut result = self.replace_uses(old_var, new_var);
for phi in &mut self.phi_nodes {
for operand in phi.operands_mut() {
if operand.value() == old_var {
*operand = PhiOperand::new(new_var, operand.predecessor());
result.replaced = result.replaced.saturating_add(1);
}
}
}
result
}
#[must_use]
pub fn find_phi_defining(&self, var: SsaVarId) -> Option<&PhiNode> {
self.phi_nodes.iter().find(|phi| phi.result() == var)
}
#[must_use]
pub fn is_trampoline(&self) -> Option<usize> {
if !self.phi_nodes.is_empty() {
return None;
}
if self.instructions.len() != 1 {
return None;
}
match self.instructions.first()?.op() {
SsaOp::Jump { target } | SsaOp::Leave { target } => Some(*target),
_ => None,
}
}
pub fn defined_variables(&self) -> impl Iterator<Item = SsaVarId> + '_ {
let phi_defs = self.phi_nodes.iter().map(PhiNode::result);
let instr_defs = self.instructions.iter().filter_map(SsaInstruction::def);
phi_defs.chain(instr_defs)
}
pub fn used_variables(&self) -> impl Iterator<Item = SsaVarId> + '_ {
let phi_uses = self.phi_nodes.iter().flat_map(PhiNode::used_variables);
let instr_uses = self.instructions.iter().flat_map(SsaInstruction::uses);
phi_uses.chain(instr_uses)
}
pub fn sort_instructions_topologically(&mut self) -> bool {
if self.instructions.len() <= 1 {
return true;
}
let mut terminators: Vec<(usize, SsaInstruction<T>)> = Vec::new();
let mut non_terminator_indices: Vec<usize> = Vec::new();
for (idx, instr) in self.instructions.iter().enumerate() {
if instr.is_terminator() {
terminators.push((idx, instr.clone()));
} else {
non_terminator_indices.push(idx);
}
}
if non_terminator_indices.is_empty() {
return true;
}
let mut def_index: HashMap<SsaVarId, usize> = HashMap::new();
for &idx in &non_terminator_indices {
let Some(instr) = self.instructions.get(idx) else {
continue;
};
if let Some(dest) = instr.def() {
def_index.insert(dest, idx);
}
}
let max_phi_var = self
.phi_nodes
.iter()
.map(|phi| phi.result().index())
.max()
.map_or(0, |m| m.saturating_add(1));
let mut phi_defs = BitSet::new(max_phi_var);
for phi in &self.phi_nodes {
phi_defs.insert(phi.result().index());
}
let idx_to_pos: HashMap<usize, usize> = non_terminator_indices
.iter()
.enumerate()
.map(|(pos, &idx)| (idx, pos))
.collect();
let n = non_terminator_indices.len();
let mut deps: Vec<BitSet> = (0..n).map(|_| BitSet::new(n)).collect();
let mut rdeps: Vec<BitSet> = (0..n).map(|_| BitSet::new(n)).collect();
let mut prev_side_effect_pos: Option<usize> = None;
for (pos, &idx) in non_terminator_indices.iter().enumerate() {
let Some(instr) = self.instructions.get(idx) else {
continue;
};
for used in &instr.uses() {
if used.index() < phi_defs.len() && phi_defs.contains(used.index()) {
continue;
}
if let Some(&dep_idx) = def_index.get(used) {
if dep_idx != idx {
if let Some(&dep_pos) = idx_to_pos.get(&dep_idx) {
if let Some(d) = deps.get_mut(pos) {
d.insert(dep_pos);
}
if let Some(r) = rdeps.get_mut(dep_pos) {
r.insert(pos);
}
}
}
}
}
if !instr.op().is_pure() {
if let Some(prev_pos) = prev_side_effect_pos {
if let Some(d) = deps.get_mut(pos) {
d.insert(prev_pos);
}
if let Some(r) = rdeps.get_mut(prev_pos) {
r.insert(pos);
}
}
prev_side_effect_pos = Some(pos);
}
}
let mut in_degree: Vec<usize> = deps.iter().map(BitSet::count).collect();
let mut ready: VecDeque<usize> = VecDeque::new();
for (pos, °) in in_degree.iter().enumerate() {
if deg == 0 {
ready.push_back(pos);
}
}
let mut sorted_positions: Vec<usize> = Vec::with_capacity(n);
while let Some(pos) = ready.pop_front() {
sorted_positions.push(pos);
let Some(rd) = rdeps.get(pos) else {
continue;
};
for dep_pos in rd.iter() {
if let Some(slot) = in_degree.get_mut(dep_pos) {
*slot = slot.saturating_sub(1);
if *slot == 0 {
ready.push_back(dep_pos);
}
}
}
}
if sorted_positions.len() != n {
return false;
}
let mut temp: Vec<Option<SsaInstruction<T>>> =
self.instructions.drain(..).map(Some).collect();
for pos in sorted_positions {
let Some(&original_idx) = non_terminator_indices.get(pos) else {
continue;
};
if let Some(instr) = temp.get_mut(original_idx).and_then(Option::take) {
self.instructions.push(instr);
}
}
terminators.sort_by_key(|(idx, _)| *idx);
for (_, instr) in terminators {
self.instructions.push(instr);
}
true
}
}
impl<T: Target> fmt::Display for SsaBlock<T>
where
SsaInstruction<T>: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "B{}:", self.id)?;
for phi in &self.phi_nodes {
writeln!(f, " {phi}")?;
}
for instr in &self.instructions {
writeln!(f, " {instr}")?;
}
Ok(())
}
}