use indexmap::IndexMap;
use rustc_hash::FxHashMap;
use std::collections::HashSet;
use sway_utils::mapped_stack::MappedStack;
use crate::{
AnalysisResults, Block, BranchToWithArgs, Constant, Context, DomFronts, DomTree, Function,
InstOp, Instruction, IrError, LocalVar, Pass, PassMutability, PostOrder, ScopedPass, Type,
Value, ValueDatum, DOMINATORS_NAME, DOM_FRONTS_NAME, POSTORDER_NAME,
};
pub const MEM2REG_NAME: &str = "mem2reg";
pub fn create_mem2reg_pass() -> Pass {
Pass {
name: MEM2REG_NAME,
descr: "Promotion of memory to SSA registers",
deps: vec![POSTORDER_NAME, DOMINATORS_NAME, DOM_FRONTS_NAME],
runner: ScopedPass::FunctionPass(PassMutability::Transform(promote_to_registers)),
}
}
fn get_validate_local_var(
context: &Context,
function: &Function,
val: &Value,
) -> Option<(String, LocalVar)> {
match context.values[val.0].value {
ValueDatum::Instruction(Instruction {
op: InstOp::GetLocal(local_var),
..
}) => {
let name = function.lookup_local_name(context, &local_var);
name.map(|name| (name.clone(), local_var))
}
_ => None,
}
}
fn is_promotable_type(context: &Context, ty: Type) -> bool {
ty.is_unit(context)
|| ty.is_bool(context)
|| ty.is_ptr(context)
|| (ty.is_uint(context) && ty.get_uint_width(context).unwrap() <= 64)
}
fn filter_usable_locals(context: &mut Context, function: &Function) -> HashSet<String> {
let mut locals: HashSet<String> = function
.locals_iter(context)
.filter_map(|(name, var)| {
let ty = var.get_inner_type(context);
is_promotable_type(context, ty).then_some(name.clone())
})
.collect();
for (_, inst) in function.instruction_iter(context) {
match context.values[inst.0].value {
ValueDatum::Instruction(Instruction {
op: InstOp::Load(_),
..
}) => {}
ValueDatum::Instruction(Instruction {
op:
InstOp::Store {
dst_val_ptr: _,
stored_val,
},
..
}) => {
if let Some((local, _)) = get_validate_local_var(context, function, &stored_val) {
locals.remove(&local);
}
}
_ => {
let operands = inst.get_instruction(context).unwrap().op.get_operands();
for opd in operands {
if let Some((local, ..)) = get_validate_local_var(context, function, &opd) {
locals.remove(&local);
}
}
}
}
}
locals
}
pub fn compute_livein(
context: &mut Context,
function: &Function,
po: &PostOrder,
locals: &HashSet<String>,
) -> FxHashMap<Block, HashSet<String>> {
let mut result = FxHashMap::<Block, HashSet<String>>::default();
for block in &po.po_to_block {
result.insert(*block, HashSet::<String>::default());
}
let mut changed = true;
while changed {
changed = false;
for block in &po.po_to_block {
let mut cur_live = HashSet::<String>::default();
for BranchToWithArgs { block: succ, .. } in block.successors(context) {
let succ_livein = &result[&succ];
cur_live.extend(succ_livein.iter().cloned());
}
for inst in block.instruction_iter(context).rev() {
match context.values[inst.0].value {
ValueDatum::Instruction(Instruction {
op: InstOp::Load(ptr),
..
}) => {
let local_var = get_validate_local_var(context, function, &ptr);
match local_var {
Some((local, ..)) if locals.contains(&local) => {
cur_live.insert(local);
}
_ => {}
}
}
ValueDatum::Instruction(Instruction {
op: InstOp::Store { dst_val_ptr, .. },
..
}) => {
let local_var = get_validate_local_var(context, function, &dst_val_ptr);
match local_var {
Some((local, _)) if locals.contains(&local) => {
cur_live.remove(&local);
}
_ => (),
}
}
_ => (),
}
}
if result[block] != cur_live {
result.get_mut(block).unwrap().extend(cur_live);
changed = true;
}
}
}
result
}
fn promote_globals(context: &mut Context, function: &Function) -> Result<bool, IrError> {
let mut replacements = FxHashMap::<Value, Constant>::default();
for (_, inst) in function.instruction_iter(context) {
if let ValueDatum::Instruction(Instruction {
op: InstOp::Load(ptr),
..
}) = context.values[inst.0].value
{
if let ValueDatum::Instruction(Instruction {
op: InstOp::GetGlobal(global_var),
..
}) = context.values[ptr.0].value
{
if !global_var.is_mutable(context)
&& is_promotable_type(context, global_var.get_inner_type(context))
{
let constant = *global_var
.get_initializer(context)
.expect("`global_var` is not mutable so it must be initialized");
replacements.insert(inst, constant);
}
}
}
}
if replacements.is_empty() {
return Ok(false);
}
let mut modified = false;
let replacements = replacements
.into_iter()
.map(|(k, v)| (k, Value::new_constant(context, v)))
.collect::<FxHashMap<_, _>>();
modified |= function.replace_values(context, &replacements, None);
Ok(modified)
}
pub fn promote_to_registers(
context: &mut Context,
analyses: &AnalysisResults,
function: Function,
) -> Result<bool, IrError> {
let mut modified = false;
modified |= promote_globals(context, &function)?;
modified |= promote_locals(context, analyses, function)?;
Ok(modified)
}
pub fn promote_locals(
context: &mut Context,
analyses: &AnalysisResults,
function: Function,
) -> Result<bool, IrError> {
let safe_locals = filter_usable_locals(context, &function);
if safe_locals.is_empty() {
return Ok(false);
}
let po: &PostOrder = analyses.get_analysis_result(function);
let dom_tree: &DomTree = analyses.get_analysis_result(function);
let dom_fronts: &DomFronts = analyses.get_analysis_result(function);
let liveins = compute_livein(context, &function, po, &safe_locals);
let mut new_phi_tracker = HashSet::<(String, Block)>::new();
let mut worklist = Vec::<(String, Type, Block)>::new();
let mut phi_to_local = FxHashMap::<Value, String>::default();
for (block, inst) in po
.po_to_block
.iter()
.rev()
.flat_map(|b| b.instruction_iter(context).map(|i| (*b, i)))
{
if let ValueDatum::Instruction(Instruction {
op: InstOp::Store { dst_val_ptr, .. },
..
}) = context.values[inst.0].value
{
match get_validate_local_var(context, &function, &dst_val_ptr) {
Some((local, var)) if safe_locals.contains(&local) => {
worklist.push((local, var.get_inner_type(context), block));
}
_ => (),
}
}
}
while let Some((local, ty, known_def)) = worklist.pop() {
for df in dom_fronts[&known_def].iter() {
if !new_phi_tracker.contains(&(local.clone(), *df)) && liveins[df].contains(&local) {
let index = df.new_arg(context, ty);
phi_to_local.insert(df.get_arg(context, index).unwrap(), local.clone());
new_phi_tracker.insert((local.clone(), *df));
worklist.push((local.clone(), ty, *df));
}
}
}
let mut name_stack = MappedStack::<String, Value>::default();
let mut value_replacement = FxHashMap::<Value, Value>::default();
let mut delete_insts = Vec::<(Block, Value)>::new();
let mut modified = record_rewrites(
context,
&function,
dom_tree,
function.get_entry_block(context),
&safe_locals,
&phi_to_local,
&mut name_stack,
&mut value_replacement,
&mut delete_insts,
);
modified |= function.replace_values(context, &value_replacement, None);
for (block, inst) in delete_insts {
modified |= block.remove_instruction(context, inst);
}
Ok(modified)
}
#[allow(clippy::too_many_arguments)]
fn record_rewrites(
context: &mut Context,
function: &Function,
dom_tree: &DomTree,
node: Block,
safe_locals: &HashSet<String>,
phi_to_local: &FxHashMap<Value, String>,
name_stack: &mut MappedStack<String, Value>,
rewrites: &mut FxHashMap<Value, Value>,
deletes: &mut Vec<(Block, Value)>,
) -> bool {
let mut modified = false;
let mut num_local_pushes = IndexMap::<String, u32>::new();
for arg in node.arg_iter(context) {
if let Some(local) = phi_to_local.get(arg) {
name_stack.push(local.clone(), *arg);
num_local_pushes
.entry(local.clone())
.and_modify(|count| *count += 1)
.or_insert(1);
}
}
for inst in node.instruction_iter(context) {
match context.values[inst.0].value {
ValueDatum::Instruction(Instruction {
op: InstOp::Load(ptr),
..
}) => {
let local_var = get_validate_local_var(context, function, &ptr);
match local_var {
Some((local, var)) if safe_locals.contains(&local) => {
let new_val = match name_stack.get(&local) {
Some(val) => *val,
None => {
let constant = *var
.get_initializer(context)
.expect("We're dealing with an uninitialized value");
Value::new_constant(context, constant)
}
};
rewrites.insert(inst, new_val);
deletes.push((node, inst));
}
_ => (),
}
}
ValueDatum::Instruction(Instruction {
op:
InstOp::Store {
dst_val_ptr,
stored_val,
},
..
}) => {
let local_var = get_validate_local_var(context, function, &dst_val_ptr);
match local_var {
Some((local, _)) if safe_locals.contains(&local) => {
name_stack.push(local.clone(), stored_val);
num_local_pushes
.entry(local)
.and_modify(|count| *count += 1)
.or_insert(1);
deletes.push((node, inst));
}
_ => (),
}
}
_ => (),
}
}
for BranchToWithArgs { block: succ, .. } in node.successors(context) {
let args: Vec<_> = succ.arg_iter(context).copied().collect();
for arg in args {
if let Some(local) = phi_to_local.get(&arg) {
let ptr = function.get_local_var(context, local).unwrap();
let new_val = match name_stack.get(local) {
Some(val) => *val,
None => {
let constant = *ptr
.get_initializer(context)
.expect("We're dealing with an uninitialized value");
Value::new_constant(context, constant)
}
};
modified = true;
let params = node.get_succ_params_mut(context, &succ).unwrap();
params.push(new_val);
}
}
}
for child in dom_tree.children(node) {
modified |= record_rewrites(
context,
function,
dom_tree,
child,
safe_locals,
phi_to_local,
name_stack,
rewrites,
deletes,
);
}
for (local, pushes) in num_local_pushes.iter() {
for _ in 0..*pushes {
name_stack.pop(local);
}
}
modified
}