mod canonical;
mod duplication;
mod kind;
mod queries;
mod rebuild;
mod repair;
mod transforms;
pub use kind::FunctionKind;
pub use queries::{MethodPurity, ReturnInfo};
pub use transforms::TrivialPhiOptions;
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
};
use crate::{
analysis::verifier::{SsaVerifier, VerifyLevel},
ir::{
block::SsaBlock,
exception::SsaExceptionHandler,
instruction::SsaInstruction,
ops::SsaOp,
phi::{PhiNode, PhiOperand},
variable::{DefSite, FunctionVarAllocator, SsaVarId, SsaVariable, VariableOrigin},
},
target::Target,
};
#[derive(Debug, Clone)]
pub struct SsaFunction<T: Target> {
blocks: Vec<SsaBlock<T>>,
variables: Vec<SsaVariable<T>>,
var_allocator: FunctionVarAllocator,
origin_versions: BTreeMap<VariableOrigin, Vec<SsaVarId>>,
origin_types: BTreeMap<VariableOrigin, T::Type>,
num_args: usize,
num_locals: usize,
original_num_locals: usize,
preserved_dispatch_vars: BTreeSet<SsaVarId>,
original_local_types: Option<Vec<T::LocalSignature>>,
exception_handlers: Vec<SsaExceptionHandler<T>>,
rename_groups: Vec<u32>,
kind: FunctionKind,
}
impl<T: Target> SsaFunction<T> {
#[must_use]
pub fn new(num_args: usize, num_locals: usize) -> Self {
Self {
blocks: Vec::new(),
variables: Vec::new(),
var_allocator: FunctionVarAllocator::new(),
origin_versions: BTreeMap::new(),
origin_types: BTreeMap::new(),
num_args,
num_locals,
original_num_locals: num_locals,
preserved_dispatch_vars: BTreeSet::new(),
original_local_types: None,
exception_handlers: Vec::new(),
rename_groups: Vec::new(),
kind: FunctionKind::Normal,
}
}
#[must_use]
pub fn with_capacity(
num_args: usize,
num_locals: usize,
block_capacity: usize,
var_capacity: usize,
) -> Self {
Self {
blocks: Vec::with_capacity(block_capacity),
variables: Vec::with_capacity(var_capacity),
var_allocator: FunctionVarAllocator::new(),
origin_versions: BTreeMap::new(),
origin_types: BTreeMap::new(),
num_args,
num_locals,
original_num_locals: num_locals,
preserved_dispatch_vars: BTreeSet::new(),
original_local_types: None,
exception_handlers: Vec::new(),
rename_groups: Vec::with_capacity(var_capacity),
kind: FunctionKind::Normal,
}
}
#[must_use]
pub fn blocks(&self) -> &[SsaBlock<T>] {
&self.blocks
}
pub fn iter_blocks(&self) -> impl Iterator<Item = (usize, &SsaBlock<T>)> {
self.blocks.iter().enumerate()
}
pub fn iter_instructions(&self) -> impl Iterator<Item = (usize, usize, &SsaInstruction<T>)> {
self.blocks
.iter()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.instructions()
.iter()
.enumerate()
.map(move |(instr_idx, instr)| (block_idx, instr_idx, instr))
})
}
pub fn iter_instructions_mut(
&mut self,
) -> impl Iterator<Item = (usize, usize, &mut SsaInstruction<T>)> {
self.blocks
.iter_mut()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.instructions_mut()
.iter_mut()
.enumerate()
.map(move |(instr_idx, instr)| (block_idx, instr_idx, instr))
})
}
pub fn iter_phis(&self) -> impl Iterator<Item = (usize, usize, &PhiNode)> {
self.blocks
.iter()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.phi_nodes()
.iter()
.enumerate()
.map(move |(phi_idx, phi)| (block_idx, phi_idx, phi))
})
}
pub fn blocks_mut(&mut self) -> &mut Vec<SsaBlock<T>> {
&mut self.blocks
}
#[must_use]
pub fn variables(&self) -> &[SsaVariable<T>] {
&self.variables
}
pub fn variables_mut(&mut self) -> &mut Vec<SsaVariable<T>> {
&mut self.variables
}
#[must_use]
pub const fn num_args(&self) -> usize {
self.num_args
}
#[must_use]
pub const fn num_locals(&self) -> usize {
self.num_locals
}
#[must_use]
pub const fn original_num_locals(&self) -> usize {
self.original_num_locals
}
pub fn set_num_locals(&mut self, num_locals: usize, original_num_locals: usize) {
self.num_locals = num_locals;
self.original_num_locals = original_num_locals;
}
#[must_use]
pub fn block_count(&self) -> usize {
self.blocks.len()
}
#[must_use]
pub fn variable_count(&self) -> usize {
self.variables.len()
}
#[must_use]
pub fn var_id_capacity(&self) -> usize {
let from_vars = self
.variables
.iter()
.map(|v| v.id().index().saturating_add(1))
.max()
.unwrap_or(0);
let from_blocks = self
.blocks
.iter()
.flat_map(|b| {
let phi_ids = b.phi_nodes().iter().flat_map(|p| {
std::iter::once(p.result().index())
.chain(p.operands().iter().map(|op| op.value().index()))
});
let instr_ids = b.instructions().iter().flat_map(|i| {
i.op()
.dest()
.into_iter()
.chain(i.op().uses())
.map(|v| v.index())
});
phi_ids.chain(instr_ids)
})
.max()
.map_or(0, |m| m.saturating_add(1));
from_vars.max(from_blocks).max(self.variables.len())
}
#[must_use]
pub fn versions_of(&self, origin: VariableOrigin) -> &[SsaVarId] {
self.origin_versions
.get(&origin)
.map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn latest_version(&self, origin: VariableOrigin) -> Option<SsaVarId> {
self.origin_versions
.get(&origin)
.and_then(|v| v.last().copied())
}
#[must_use]
pub fn var_index(&self, id: SsaVarId) -> Option<usize> {
let idx = id.index();
if idx < self.variables.len() {
Some(idx)
} else {
None
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
#[must_use]
pub fn block(&self, index: usize) -> Option<&SsaBlock<T>> {
self.blocks.get(index)
}
pub fn block_mut(&mut self, index: usize) -> Option<&mut SsaBlock<T>> {
self.blocks.get_mut(index)
}
#[must_use]
pub fn variable(&self, id: SsaVarId) -> Option<&SsaVariable<T>> {
self.variables.get(id.index())
}
pub fn variable_mut(&mut self, id: SsaVarId) -> Option<&mut SsaVariable<T>> {
self.variables.get_mut(id.index())
}
pub fn add_block(&mut self, block: SsaBlock<T>) {
self.blocks.push(block);
}
pub fn create_variable(
&mut self,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: T::Type,
) -> SsaVarId {
let id = self.var_allocator.alloc();
let var = SsaVariable::new(id, origin, version, def_site, var_type.clone());
debug_assert_eq!(id.index(), self.variables.len());
self.origin_versions.entry(origin).or_default().push(id);
if !T::is_unknown(&var_type) && !self.origin_types.contains_key(&origin) {
self.origin_types.insert(origin, var_type);
}
self.variables.push(var);
if self.rename_groups.len() <= id.index() {
self.rename_groups
.resize(id.index().saturating_add(1), u32::MAX);
}
id
}
pub fn create_variable_for_origin(
&mut self,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
) -> SsaVarId {
let var_type = self.origin_type(origin);
self.create_variable(origin, version, def_site, var_type)
}
pub fn register_origin_type(&mut self, origin: VariableOrigin, var_type: T::Type) {
if !T::is_unknown(&var_type) && !self.origin_types.contains_key(&origin) {
self.origin_types.insert(origin, var_type);
}
}
#[must_use]
pub fn origin_type(&self, origin: VariableOrigin) -> T::Type {
self.origin_types
.get(&origin)
.cloned()
.unwrap_or_else(T::unknown_type)
}
#[must_use]
pub fn origin_types(&self) -> &BTreeMap<VariableOrigin, T::Type> {
&self.origin_types
}
fn rebuild_origin_versions(&mut self) {
self.origin_versions.clear();
for var in &self.variables {
self.origin_versions
.entry(var.origin())
.or_default()
.push(var.id());
}
}
fn reassign_dense_ids(&mut self) -> BTreeMap<SsaVarId, SsaVarId> {
let mut remap = BTreeMap::new();
let old_groups = std::mem::take(&mut self.rename_groups);
self.var_allocator = FunctionVarAllocator::starting_from(self.variables.len());
let mut new_groups = vec![u32::MAX; self.variables.len()];
for (index, var) in self.variables.iter_mut().enumerate() {
let old_id = var.id();
let new_id = SsaVarId::from_index(index);
if let Some(&old_group) = old_groups.get(old_id.index()) {
if let Some(slot) = new_groups.get_mut(index) {
*slot = old_group;
}
}
if old_id != new_id {
remap.insert(old_id, new_id);
var.set_id(new_id);
}
}
self.rename_groups = new_groups;
remap
}
fn remap_var_ids_in_blocks(&mut self, remap: &BTreeMap<SsaVarId, SsaVarId>) {
if remap.is_empty() {
return;
}
let lookup = |id: SsaVarId| -> Option<SsaVarId> { remap.get(&id).copied() };
let resolve = |id: SsaVarId| -> SsaVarId { remap.get(&id).copied().unwrap_or(id) };
for block in &mut self.blocks {
for phi in block.phi_nodes_mut() {
let old_result = phi.result();
phi.set_result(resolve(old_result));
for operand in phi.operands_mut() {
let old_value = operand.value();
*operand = PhiOperand::new(resolve(old_value), operand.predecessor());
}
}
for instr in block.instructions_mut() {
let new_op = instr.op().remap_variables(lookup);
instr.set_op(new_op);
}
}
let remapped_dispatch: BTreeSet<SsaVarId> = self
.preserved_dispatch_vars
.iter()
.map(|id| resolve(*id))
.collect();
self.preserved_dispatch_vars = remapped_dispatch;
}
pub fn mark_preserved_dispatch_var(&mut self, var: SsaVarId) {
self.preserved_dispatch_vars.insert(var);
}
#[must_use]
pub fn is_preserved_dispatch_var(&self, var: SsaVarId) -> bool {
self.preserved_dispatch_vars.contains(&var)
}
#[must_use]
pub fn has_preserved_dispatch_vars(&self) -> bool {
!self.preserved_dispatch_vars.is_empty()
}
pub fn set_original_local_types(&mut self, types: Vec<T::LocalSignature>) {
self.original_local_types = Some(types);
}
#[must_use]
pub fn original_local_types(&self) -> Option<&[T::LocalSignature]> {
self.original_local_types.as_deref()
}
pub fn set_exception_handlers(&mut self, handlers: Vec<SsaExceptionHandler<T>>) {
self.exception_handlers = handlers;
}
#[must_use]
pub fn exception_handlers(&self) -> &[SsaExceptionHandler<T>] {
&self.exception_handlers
}
#[must_use]
pub fn has_exception_handlers(&self) -> bool {
!self.exception_handlers.is_empty()
}
#[must_use]
pub fn has_interrupt_return(&self) -> bool {
self.blocks.iter().any(|block| {
block
.instructions()
.iter()
.any(|instr| matches!(instr.op(), SsaOp::InterruptReturn))
})
}
#[must_use]
pub fn kind(&self) -> FunctionKind {
self.kind
}
pub fn set_kind(&mut self, kind: FunctionKind) {
self.kind = kind;
}
#[must_use]
pub fn rename_group(&self, var_id: SsaVarId) -> u32 {
self.rename_groups
.get(var_id.index())
.copied()
.unwrap_or(u32::MAX)
}
pub fn set_rename_group(&mut self, var_id: SsaVarId, group: u32) {
let idx = var_id.index();
if idx >= self.rename_groups.len() {
self.rename_groups.resize(idx.saturating_add(1), u32::MAX);
}
if let Some(slot) = self.rename_groups.get_mut(idx) {
*slot = group;
}
}
pub fn sort_all_blocks_topologically(&mut self) -> bool {
let mut all_sorted = true;
for block in &mut self.blocks {
if !block.sort_instructions_topologically() {
all_sorted = false;
}
}
all_sorted
}
pub fn validate_types(&self) -> Result<(), String> {
for var in &self.variables {
if !T::is_unknown(var.var_type()) || var.uses().is_empty() {
continue;
}
let has_meaningful_use = var.uses().iter().any(|use_site| {
if use_site.is_phi_operand {
if let Some(block) = self.block(use_site.block) {
if let Some(phi) = block.phi(use_site.instruction) {
if let Some(result_var) = self.variable(phi.result()) {
return !T::is_unknown(result_var.var_type());
}
}
}
return false;
}
if let Some(block) = self.block(use_site.block) {
if let Some(instr) = block.instruction(use_site.instruction) {
return !matches!(instr.op(), SsaOp::Pop { .. });
}
}
true });
if has_meaningful_use {
let use_details: Vec<String> = var
.uses()
.iter()
.map(|use_site| {
if use_site.is_phi_operand {
return format!("phi in block {}", use_site.block);
}
if let Some(block) = self.block(use_site.block) {
if let Some(instr) = block.instruction(use_site.instruction) {
return format!(
"block {} instr {}: {:?}",
use_site.block,
use_site.instruction,
instr.op()
);
}
}
format!(
"block {} instr {}: <unknown>",
use_site.block, use_site.instruction
)
})
.collect();
return Err(format!(
"Variable {} (origin={:?}) has Unknown type but is used ({} uses): [{}]",
var.id(),
var.origin(),
var.uses().len(),
use_details.join(", ")
));
}
}
Ok(())
}
}
impl<T: Target> SsaFunction<T> {
pub fn rebuild_ssa(&mut self) -> crate::Result<()> {
if self.blocks.is_empty() {
return Ok(());
}
rebuild::SsaRebuilder::new(self).rebuild()
}
pub fn validate(&self) -> Result<(), String> {
let errors = SsaVerifier::new(self).verify(VerifyLevel::Standard);
if errors.is_empty() {
Ok(())
} else {
Err(errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; "))
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.validate().is_ok()
}
}
impl<T: Target> fmt::Display for SsaFunction<T>
where
SsaBlock<T>: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"SSA Function ({}, {} args, {} locals):",
self.kind, self.num_args, self.num_locals
)?;
writeln!(f, " Variables: {}", self.variables.len())?;
writeln!(f, " Blocks: {}", self.blocks.len())?;
writeln!(f)?;
for block in &self.blocks {
write!(f, "{block}")?;
}
Ok(())
}
}