use super::cfg::{BasicBlock, ControlFlowGraph, Instr};
use crate::codegen::Expression;
use indexmap::IndexMap;
use std::collections::HashSet;
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct Def {
pub block_no: usize,
pub instr_no: usize,
pub assignment_no: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Transfer {
Gen { def: Def, var_no: usize },
Mod { var_no: usize },
Copy { var_no: usize, src: usize },
Kill { var_no: usize },
}
impl fmt::Display for Transfer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Transfer::Gen { def, var_no } => {
write!(f, "Gen %{var_no} = ({}, {})", def.block_no, def.instr_no)
}
Transfer::Mod { var_no } => {
write!(f, "Mod %{var_no}")
}
Transfer::Copy { var_no, src } => {
write!(f, "Copy %{var_no} from %{src}")
}
Transfer::Kill { var_no } => {
write!(f, "Kill %{var_no}")
}
}
}
}
pub type VarDefs = IndexMap<usize, IndexMap<Def, bool>>;
pub fn find(cfg: &mut ControlFlowGraph) {
for (block_no, block) in cfg.blocks.iter_mut().enumerate() {
block.transfers = instr_transfers(block_no, block);
}
let mut blocks_todo: HashSet<usize> = HashSet::new();
blocks_todo.insert(0);
while let Some(block_no) = blocks_todo.iter().next() {
let block_no = *block_no;
blocks_todo.remove(&block_no);
let mut vars = cfg.blocks[block_no].defs.clone();
for transfers in &cfg.blocks[block_no].transfers {
apply_transfers(transfers, &mut vars);
}
for edge in cfg.blocks[block_no].successors() {
if cfg.blocks[edge].defs != vars {
let mut changed = false;
for (var_no, defs) in &vars {
if let Some(entry) = cfg.blocks[edge].defs.get_mut(var_no) {
for (incoming_def, incoming_modified) in defs {
if let Some(e) = entry.get_mut(incoming_def) {
if !*e && *incoming_modified {
*e = true;
changed = true;
}
} else {
changed |=
entry.insert(*incoming_def, *incoming_modified).is_none();
}
}
} else {
changed |= cfg.blocks[edge]
.defs
.insert(*var_no, defs.clone())
.is_none();
}
for (incoming_def, _) in defs {
if incoming_def.block_no >= edge {
changed |= cfg.blocks[edge].loop_reaching_variables.insert(*var_no);
}
}
}
if changed {
blocks_todo.insert(edge);
}
}
}
}
}
fn instr_transfers(block_no: usize, block: &BasicBlock) -> Vec<Vec<Transfer>> {
let mut transfers = Vec::new();
for (instr_no, instr) in block.instr.iter().enumerate() {
let set_var = |var_nos: &[usize]| {
let mut transfer = Vec::new();
for (assignment_no, var_no) in var_nos.iter().enumerate() {
transfer.insert(0, Transfer::Kill { var_no: *var_no });
transfer.push(Transfer::Gen {
def: Def {
block_no,
instr_no,
assignment_no,
},
var_no: *var_no,
});
}
transfer
};
transfers.push(match instr {
Instr::Set {
res,
expr: Expression::Variable { var_no: src, .. },
..
} => {
vec![
Transfer::Kill { var_no: *res },
Transfer::Copy {
var_no: *res,
src: *src,
},
]
}
Instr::Set { res, .. } => set_var(&[*res]),
Instr::Call { res, .. } => set_var(res),
Instr::LoadStorage { res, .. } | Instr::PopStorage { res: Some(res), .. } => {
set_var(&[*res])
}
Instr::PushMemory { array, res, .. } => {
let mut v = set_var(&[*res]);
v.push(Transfer::Mod { var_no: *array });
v
}
Instr::PopMemory { array, .. } => {
vec![Transfer::Mod { var_no: *array }]
}
Instr::ExternalCall {
success: Some(res), ..
}
| Instr::Constructor {
success: None, res, ..
}
| Instr::ValueTransfer {
success: Some(res), ..
} => set_var(&[*res]),
Instr::ClearStorage { storage: dest, .. }
| Instr::SetStorageBytes { storage: dest, .. }
| Instr::SetStorage { storage: dest, .. }
| Instr::Store { dest, .. } => {
let mut v = Vec::new();
if let Some(var_no) = array_var(dest) {
v.push(Transfer::Mod { var_no });
}
v
}
Instr::Constructor {
success: Some(success),
res,
..
} => set_var(&[*res, *success]),
_ => Vec::new(),
});
}
transfers
}
fn array_var(expr: &Expression) -> Option<usize> {
match expr {
Expression::Variable { var_no, .. } => Some(*var_no),
Expression::Subscript { expr, .. } | Expression::StructMember { expr, .. } => {
array_var(expr)
}
_ => None,
}
}
pub fn apply_transfers(transfers: &[Transfer], vars: &mut IndexMap<usize, IndexMap<Def, bool>>) {
for transfer in transfers {
match transfer {
Transfer::Kill { var_no } => {
vars.swap_remove(var_no);
}
Transfer::Mod { var_no } => {
if let Some(entry) = vars.get_mut(var_no) {
for e in entry.values_mut() {
*e = true;
}
}
}
Transfer::Copy { var_no, src } => {
if let Some(defs) = vars.get(src) {
let defs = defs.clone();
vars.insert(*var_no, defs);
}
}
Transfer::Gen { var_no, def } => {
if let Some(entry) = vars.get_mut(var_no) {
entry.insert(*def, false);
} else {
let mut v = IndexMap::new();
v.insert(*def, false);
vars.insert(*var_no, v);
}
}
}
}
}