use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use crate::cfg::get_cfg_context;
use crate::dfg::get_dfg_context;
use crate::types::{CfgInfo, DfgInfo, Language, RefType, VarRef};
use crate::TldrResult;
use super::analysis::{compute_live_variables, LiveVariables};
use super::dominators::{build_dominator_tree, compute_dominance_frontier, DominatorTree};
use super::types::*;
pub fn construct_minimal_ssa(cfg: &CfgInfo, dfg: &DfgInfo) -> TldrResult<SsaFunction> {
construct_minimal_ssa_with_statements(cfg, dfg, &[])
}
pub fn construct_minimal_ssa_with_statements(
cfg: &CfgInfo,
dfg: &DfgInfo,
statements: &[String],
) -> TldrResult<SsaFunction> {
let dom_tree = build_dominator_tree(cfg)?;
let dom_frontier = compute_dominance_frontier(cfg, &dom_tree)?;
let line_to_block = build_line_to_block_map(cfg);
let var_defs = collect_variable_definitions(dfg, &line_to_block);
let phi_placements = place_phi_functions(&var_defs, &dom_frontier);
let mut ssa_blocks = build_initial_ssa_blocks(cfg, &phi_placements);
let mut renaming_state = RenamingState::new();
let renaming_context = SsaRenamingContext {
dfg,
dom_tree: &dom_tree,
line_to_block: &line_to_block,
statements,
};
rename_variables_recursive(
cfg.entry_block,
&renaming_context,
&mut ssa_blocks,
&mut renaming_state,
);
fill_phi_sources(cfg, &mut ssa_blocks, &renaming_state);
let def_use = build_def_use_chains(&ssa_blocks);
let stats = compute_ssa_stats(&ssa_blocks, &renaming_state);
Ok(SsaFunction {
function: cfg.function.clone(),
file: PathBuf::new(), ssa_type: SsaType::Minimal,
blocks: ssa_blocks,
ssa_names: renaming_state.into_ssa_names(),
def_use,
stats,
})
}
pub fn construct_semi_pruned_ssa(cfg: &CfgInfo, dfg: &DfgInfo) -> TldrResult<SsaFunction> {
construct_semi_pruned_ssa_with_statements(cfg, dfg, &[])
}
pub fn construct_semi_pruned_ssa_with_statements(
cfg: &CfgInfo,
dfg: &DfgInfo,
statements: &[String],
) -> TldrResult<SsaFunction> {
let dom_tree = build_dominator_tree(cfg)?;
let dom_frontier = compute_dominance_frontier(cfg, &dom_tree)?;
let line_to_block = build_line_to_block_map(cfg);
let var_defs = collect_variable_definitions(dfg, &line_to_block);
let non_local_vars = filter_non_local_variables(dfg, &line_to_block);
let filtered_defs: HashMap<String, HashSet<usize>> = var_defs
.into_iter()
.filter(|(var, _)| non_local_vars.contains(var))
.collect();
let phi_placements = place_phi_functions(&filtered_defs, &dom_frontier);
let mut ssa_blocks = build_initial_ssa_blocks(cfg, &phi_placements);
let mut renaming_state = RenamingState::new();
let renaming_context = SsaRenamingContext {
dfg,
dom_tree: &dom_tree,
line_to_block: &line_to_block,
statements,
};
rename_variables_recursive(
cfg.entry_block,
&renaming_context,
&mut ssa_blocks,
&mut renaming_state,
);
fill_phi_sources(cfg, &mut ssa_blocks, &renaming_state);
let def_use = build_def_use_chains(&ssa_blocks);
let stats = compute_ssa_stats(&ssa_blocks, &renaming_state);
Ok(SsaFunction {
function: cfg.function.clone(),
file: PathBuf::new(),
ssa_type: SsaType::SemiPruned,
blocks: ssa_blocks,
ssa_names: renaming_state.into_ssa_names(),
def_use,
stats,
})
}
pub fn construct_pruned_ssa(
cfg: &CfgInfo,
dfg: &DfgInfo,
live_vars: &LiveVariables,
) -> TldrResult<SsaFunction> {
construct_pruned_ssa_with_statements(cfg, dfg, live_vars, &[])
}
pub fn construct_pruned_ssa_with_statements(
cfg: &CfgInfo,
dfg: &DfgInfo,
live_vars: &LiveVariables,
statements: &[String],
) -> TldrResult<SsaFunction> {
let dom_tree = build_dominator_tree(cfg)?;
let dom_frontier = compute_dominance_frontier(cfg, &dom_tree)?;
let line_to_block = build_line_to_block_map(cfg);
let var_defs = collect_variable_definitions(dfg, &line_to_block);
let phi_placements = place_phi_functions_pruned(&var_defs, &dom_frontier, live_vars);
let mut ssa_blocks = build_initial_ssa_blocks(cfg, &phi_placements);
let mut renaming_state = RenamingState::new();
let renaming_context = SsaRenamingContext {
dfg,
dom_tree: &dom_tree,
line_to_block: &line_to_block,
statements,
};
rename_variables_recursive(
cfg.entry_block,
&renaming_context,
&mut ssa_blocks,
&mut renaming_state,
);
fill_phi_sources(cfg, &mut ssa_blocks, &renaming_state);
let def_use = build_def_use_chains(&ssa_blocks);
let stats = compute_ssa_stats(&ssa_blocks, &renaming_state);
Ok(SsaFunction {
function: cfg.function.clone(),
file: PathBuf::new(),
ssa_type: SsaType::Pruned,
blocks: ssa_blocks,
ssa_names: renaming_state.into_ssa_names(),
def_use,
stats,
})
}
pub fn construct_ssa(
source: &str,
function: &str,
lang: Language,
ssa_type: SsaType,
) -> TldrResult<SsaFunction> {
let cfg = get_cfg_context(source, function, lang)?;
let dfg = get_dfg_context(source, function, lang)?;
let statements: Vec<String> = source.lines().map(|l| l.to_string()).collect();
match ssa_type {
SsaType::Minimal => construct_minimal_ssa_with_statements(&cfg, &dfg, &statements),
SsaType::SemiPruned => construct_semi_pruned_ssa_with_statements(&cfg, &dfg, &statements),
SsaType::Pruned => {
match compute_live_variables(&cfg, &dfg.refs) {
Ok(live_vars) => {
construct_pruned_ssa_with_statements(&cfg, &dfg, &live_vars, &statements)
}
Err(_) => {
let mut ssa =
construct_semi_pruned_ssa_with_statements(&cfg, &dfg, &statements)?;
ssa.ssa_type = SsaType::Pruned;
Ok(ssa)
}
}
}
}
}
fn build_line_to_block_map(cfg: &CfgInfo) -> HashMap<u32, usize> {
let mut map = HashMap::new();
for block in &cfg.blocks {
for line in block.lines.0..=block.lines.1 {
map.insert(line, block.id);
}
}
map
}
fn get_block_for_line(line: u32, line_to_block: &HashMap<u32, usize>) -> Option<usize> {
line_to_block.get(&line).copied()
}
fn get_source_text(line: u32, statements: &[String]) -> Option<String> {
if statements.is_empty() || line == 0 {
return None;
}
statements.get((line as usize).wrapping_sub(1)).cloned()
}
fn resolve_uses_on_line(
line: u32,
def_var_name: &str,
dfg: &DfgInfo,
line_to_block: &HashMap<u32, usize>,
block_id: usize,
state: &RenamingState,
) -> Vec<SsaNameId> {
dfg.refs
.iter()
.filter(|r| {
r.line == line
&& r.name != def_var_name && matches!(r.ref_type, RefType::Use)
&& get_block_for_line(r.line, line_to_block) == Some(block_id)
})
.filter_map(|r| state.current(&r.name))
.collect()
}
fn collect_variable_definitions(
dfg: &DfgInfo,
line_to_block: &HashMap<u32, usize>,
) -> HashMap<String, HashSet<usize>> {
let mut var_defs: HashMap<String, HashSet<usize>> = HashMap::new();
for var_ref in &dfg.refs {
if matches!(var_ref.ref_type, RefType::Definition | RefType::Update) {
if let Some(block_id) = get_block_for_line(var_ref.line, line_to_block) {
var_defs
.entry(var_ref.name.clone())
.or_default()
.insert(block_id);
}
}
}
var_defs
}
fn filter_non_local_variables(
dfg: &DfgInfo,
line_to_block: &HashMap<u32, usize>,
) -> HashSet<String> {
let mut var_blocks: HashMap<String, HashSet<usize>> = HashMap::new();
for var_ref in &dfg.refs {
if let Some(block_id) = get_block_for_line(var_ref.line, line_to_block) {
var_blocks
.entry(var_ref.name.clone())
.or_default()
.insert(block_id);
}
}
var_blocks
.into_iter()
.filter(|(_, blocks)| blocks.len() > 1)
.map(|(var, _)| var)
.collect()
}
fn place_phi_functions(
var_defs: &HashMap<String, HashSet<usize>>,
dom_frontier: &super::dominators::DominanceFrontier,
) -> HashMap<usize, Vec<String>> {
let mut phi_placements: HashMap<usize, Vec<String>> = HashMap::new();
for (var, def_blocks) in var_defs {
let idf = dom_frontier.iterated(def_blocks);
for block in idf {
phi_placements.entry(block).or_default().push(var.clone());
}
}
phi_placements
}
fn place_phi_functions_pruned(
var_defs: &HashMap<String, HashSet<usize>>,
dom_frontier: &super::dominators::DominanceFrontier,
live_vars: &LiveVariables,
) -> HashMap<usize, Vec<String>> {
let mut phi_placements: HashMap<usize, Vec<String>> = HashMap::new();
for (var, def_blocks) in var_defs {
let idf = dom_frontier.iterated(def_blocks);
for block in idf {
let is_live = live_vars
.blocks
.get(&block)
.map(|sets| sets.live_in.contains(var))
.unwrap_or(false);
if is_live {
phi_placements.entry(block).or_default().push(var.clone());
}
}
}
phi_placements
}
fn build_initial_ssa_blocks(
cfg: &CfgInfo,
phi_placements: &HashMap<usize, Vec<String>>,
) -> Vec<SsaBlock> {
let mut predecessors: HashMap<usize, Vec<usize>> = HashMap::new();
for block in &cfg.blocks {
predecessors.entry(block.id).or_default();
}
for edge in &cfg.edges {
predecessors.entry(edge.to).or_default().push(edge.from);
}
let mut successors: HashMap<usize, Vec<usize>> = HashMap::new();
for block in &cfg.blocks {
successors.entry(block.id).or_default();
}
for edge in &cfg.edges {
successors.entry(edge.from).or_default().push(edge.to);
}
cfg.blocks
.iter()
.map(|block| {
let phi_functions = phi_placements
.get(&block.id)
.map(|vars| {
vars.iter()
.map(|var| PhiFunction {
target: SsaNameId(0), variable: var.clone(),
sources: Vec::new(), line: block.lines.0,
})
.collect()
})
.unwrap_or_default();
SsaBlock {
id: block.id,
label: if cfg.entry_block == block.id {
Some("entry".to_string())
} else if cfg.exit_blocks.contains(&block.id) {
Some("exit".to_string())
} else {
None
},
lines: block.lines,
phi_functions,
instructions: Vec::new(),
successors: successors.get(&block.id).cloned().unwrap_or_default(),
predecessors: predecessors.get(&block.id).cloned().unwrap_or_default(),
}
})
.collect()
}
pub struct RenamingState {
next_id: u32,
stacks: HashMap<String, Vec<SsaNameId>>,
names: HashMap<SsaNameId, SsaName>,
counters: HashMap<String, u32>,
block_stacks: HashMap<String, Vec<(SsaNameId, usize)>>,
block_exit_versions: HashMap<(String, usize), SsaNameId>,
moved_vars: HashSet<String>,
}
impl RenamingState {
fn new() -> Self {
Self {
next_id: 1, stacks: HashMap::new(),
names: HashMap::new(),
counters: HashMap::new(),
block_stacks: HashMap::new(),
block_exit_versions: HashMap::new(),
moved_vars: HashSet::new(),
}
}
fn new_name(&mut self, var: &str, block_id: usize, line: u32) -> SsaNameId {
let id = SsaNameId(self.next_id);
self.next_id += 1;
let version = self.counters.entry(var.to_string()).or_insert(0);
*version += 1;
let current_version = *version;
let name = SsaName {
id,
variable: var.to_string(),
version: current_version,
def_block: Some(block_id),
def_line: line,
};
self.names.insert(id, name);
self.stacks.entry(var.to_string()).or_default().push(id);
self.block_stacks
.entry(var.to_string())
.or_default()
.push((id, block_id));
id
}
fn current(&self, var: &str) -> Option<SsaNameId> {
self.stacks.get(var).and_then(|s| s.last().copied())
}
fn stack_depth(&self, var: &str) -> usize {
self.stacks.get(var).map(|s| s.len()).unwrap_or(0)
}
fn restore_depth(&mut self, var: &str, target_depth: usize) {
if let Some(stack) = self.stacks.get_mut(var) {
while stack.len() > target_depth {
stack.pop();
}
}
if let Some(block_stack) = self.block_stacks.get_mut(var) {
while block_stack.len() > target_depth {
block_stack.pop();
}
}
}
fn into_ssa_names(self) -> Vec<SsaName> {
let mut names: Vec<_> = self.names.into_values().collect();
names.sort_by_key(|n| n.id.0);
names
}
}
struct SsaRenamingContext<'a> {
dfg: &'a DfgInfo,
dom_tree: &'a DominatorTree,
line_to_block: &'a HashMap<u32, usize>,
statements: &'a [String],
}
fn rename_variables_recursive(
block_id: usize,
context: &SsaRenamingContext<'_>,
ssa_blocks: &mut [SsaBlock],
state: &mut RenamingState,
) {
let ssa_block_idx = ssa_blocks.iter().position(|b| b.id == block_id);
if ssa_block_idx.is_none() {
return;
}
let ssa_block_idx = ssa_block_idx.unwrap();
let mut initial_depths: HashMap<String, usize> = HashMap::new();
let block_lines = ssa_blocks[ssa_block_idx].lines;
let phi_vars: Vec<String> = ssa_blocks[ssa_block_idx]
.phi_functions
.iter()
.map(|phi| phi.variable.clone())
.collect();
for var in &phi_vars {
initial_depths
.entry(var.clone())
.or_insert_with(|| state.stack_depth(var));
}
for (idx, var) in phi_vars.iter().enumerate() {
let new_id = state.new_name(var, block_id, block_lines.0);
ssa_blocks[ssa_block_idx].phi_functions[idx].target = new_id;
}
let refs_in_block: Vec<&VarRef> = context
.dfg
.refs
.iter()
.filter(|r| get_block_for_line(r.line, context.line_to_block) == Some(block_id))
.collect();
let mut refs_sorted = refs_in_block;
refs_sorted.sort_by_key(|r| (r.line, matches!(r.ref_type, RefType::Use)));
let mut touched_vars: HashSet<String> = phi_vars.iter().cloned().collect();
for var_ref in refs_sorted {
initial_depths
.entry(var_ref.name.clone())
.or_insert_with(|| state.stack_depth(&var_ref.name));
touched_vars.insert(var_ref.name.clone());
match var_ref.ref_type {
RefType::Definition => {
let rhs_uses = resolve_uses_on_line(
var_ref.line,
&var_ref.name,
context.dfg,
context.line_to_block,
block_id,
state,
);
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
ssa_blocks[ssa_block_idx].instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: rhs_uses,
line: var_ref.line,
source_text: get_source_text(var_ref.line, context.statements),
});
}
RefType::Update => {
let use_version = state.current(&var_ref.name);
let mut all_uses: Vec<SsaNameId> = resolve_uses_on_line(
var_ref.line,
&var_ref.name,
context.dfg,
context.line_to_block,
block_id,
state,
);
if let Some(self_use) = use_version {
if !all_uses.contains(&self_use) {
all_uses.insert(0, self_use);
}
}
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
ssa_blocks[ssa_block_idx].instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: all_uses,
line: var_ref.line,
source_text: get_source_text(var_ref.line, context.statements),
});
}
RefType::Use => {
let _use_version = state.current(&var_ref.name);
}
}
}
for var in &touched_vars {
if let Some(current_id) = state.current(var) {
state
.block_exit_versions
.insert((var.clone(), block_id), current_id);
}
}
for (var, stack) in &state.stacks {
if !touched_vars.contains(var) {
if let Some(¤t_id) = stack.last() {
state
.block_exit_versions
.insert((var.clone(), block_id), current_id);
}
}
}
if let Some(node) = context.dom_tree.nodes.get(&block_id) {
for &child in &node.children {
rename_variables_recursive(
child,
context,
ssa_blocks,
state,
);
}
}
for (var, depth) in initial_depths {
state.restore_depth(&var, depth);
}
}
fn fill_phi_sources(_cfg: &CfgInfo, ssa_blocks: &mut [SsaBlock], state: &RenamingState) {
for ssa_block in ssa_blocks.iter_mut() {
if ssa_block.phi_functions.is_empty() {
continue;
}
let predecessors = ssa_block.predecessors.clone();
for phi in &mut ssa_block.phi_functions {
for &pred_id in &predecessors {
let source_version = state
.block_exit_versions
.get(&(phi.variable.clone(), pred_id))
.copied();
if let Some(version) = source_version {
phi.sources.push(PhiSource {
block: pred_id,
name: version,
});
}
}
}
}
}
fn build_def_use_chains(ssa_blocks: &[SsaBlock]) -> HashMap<SsaNameId, Vec<SsaNameId>> {
let mut def_use: HashMap<SsaNameId, Vec<SsaNameId>> = HashMap::new();
for block in ssa_blocks {
for phi in &block.phi_functions {
def_use.entry(phi.target).or_default();
for source in &phi.sources {
def_use.entry(source.name).or_default().push(phi.target);
}
}
for inst in &block.instructions {
if let Some(target) = inst.target {
def_use.entry(target).or_default();
}
for &use_id in &inst.uses {
if let Some(target) = inst.target {
def_use.entry(use_id).or_default().push(target);
}
}
}
}
def_use
}
fn compute_ssa_stats(ssa_blocks: &[SsaBlock], state: &RenamingState) -> SsaStats {
let phi_count: usize = ssa_blocks.iter().map(|b| b.phi_functions.len()).sum();
let instructions: usize = ssa_blocks.iter().map(|b| b.instructions.len()).sum();
SsaStats {
phi_count,
ssa_names: state.names.len(),
blocks: ssa_blocks.len(),
instructions,
dead_phi_count: 0, }
}
pub fn rename_variables(
_ssa: &mut SsaFunction,
_cfg: &CfgInfo,
_dom_tree: &DominatorTree,
) -> TldrResult<()> {
Ok(())
}
pub fn get_definition(ssa: &SsaFunction, name: SsaNameId) -> Option<&SsaInstruction> {
for block in &ssa.blocks {
for inst in &block.instructions {
if inst.target == Some(name) {
return Some(inst);
}
}
}
None
}
pub fn get_def_block(ssa: &SsaFunction, name: SsaNameId) -> Option<usize> {
ssa.ssa_names
.iter()
.find(|n| n.id == name)
.and_then(|n| n.def_block)
}
pub fn filter_ssa_by_variable(mut ssa: SsaFunction, variable: &str) -> SsaFunction {
let keep_ids: HashSet<SsaNameId> = ssa
.ssa_names
.iter()
.filter(|n| n.variable == variable)
.map(|n| n.id)
.collect();
ssa.ssa_names.retain(|n| n.variable == variable);
for block in &mut ssa.blocks {
block.phi_functions.retain(|phi| phi.variable == variable);
block.instructions.retain(|inst| {
inst.target.is_some_and(|t| keep_ids.contains(&t))
|| inst.uses.iter().any(|u| keep_ids.contains(u))
});
}
ssa.def_use.retain(|k, _| keep_ids.contains(k));
for uses in ssa.def_use.values_mut() {
uses.retain(|u| keep_ids.contains(u));
}
ssa
}
use crate::types::VarRefContext;
#[derive(Debug)]
pub struct LanguageConstructResult {
pub definitions: Vec<SsaNameId>,
pub uses: Vec<SsaNameId>,
pub instructions: Vec<SsaInstruction>,
}
pub fn handle_augmented_assignment(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let use_version = state.current(&var_ref.name);
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
let instruction = SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: use_version.into_iter().collect(),
line: var_ref.line,
source_text: None,
};
LanguageConstructResult {
definitions: vec![target],
uses: use_version.into_iter().collect(),
instructions: vec![instruction],
}
}
pub fn handle_multiple_assignment(
refs: &[&VarRef],
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let uses: Vec<SsaNameId> = refs
.iter()
.filter(|r| matches!(r.ref_type, RefType::Use))
.filter_map(|r| state.current(&r.name))
.collect();
let mut definitions = Vec::new();
let mut instructions = Vec::new();
for var_ref in refs
.iter()
.filter(|r| matches!(r.ref_type, RefType::Definition))
{
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
definitions.push(target);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: uses.clone(),
line: var_ref.line,
source_text: None,
});
}
LanguageConstructResult {
definitions,
uses,
instructions,
}
}
pub fn handle_walrus_operator(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
let instruction = SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: Vec::new(), line: var_ref.line,
source_text: None,
};
LanguageConstructResult {
definitions: vec![target],
uses: Vec::new(),
instructions: vec![instruction],
}
}
pub fn is_comprehension_scope(var_ref: &VarRef) -> bool {
var_ref
.context
.as_ref()
.is_some_and(|c| matches!(c, VarRefContext::ComprehensionScope))
}
pub fn handle_destructuring(
refs: &[&VarRef],
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let source_uses: Vec<SsaNameId> = refs
.iter()
.filter(|r| matches!(r.ref_type, RefType::Use))
.filter_map(|r| state.current(&r.name))
.collect();
let mut definitions = Vec::new();
let mut instructions = Vec::new();
for var_ref in refs
.iter()
.filter(|r| matches!(r.ref_type, RefType::Definition))
{
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
definitions.push(target);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: source_uses.clone(),
line: var_ref.line,
source_text: None,
});
}
LanguageConstructResult {
definitions,
uses: source_uses,
instructions,
}
}
pub fn handle_closure_capture(captured_var: &str, state: &RenamingState) -> Option<SsaNameId> {
state.current(captured_var)
}
pub fn handle_short_declaration(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
_is_new_var: bool, ) -> SsaNameId {
state.new_name(&var_ref.name, block_id, var_ref.line)
}
pub fn handle_multiple_return(
refs: &[&VarRef],
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let mut definitions = Vec::new();
let mut instructions = Vec::new();
for var_ref in refs
.iter()
.filter(|r| matches!(r.ref_type, RefType::Definition))
{
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
definitions.push(target);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: Vec::new(), line: var_ref.line,
source_text: None,
});
}
LanguageConstructResult {
definitions,
uses: Vec::new(),
instructions,
}
}
pub fn is_blank_identifier(name: &str) -> bool {
name == "_"
}
pub fn handle_rust_shadowing(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
is_new_binding: bool, ) -> SsaNameId {
if is_new_binding {
let shadow_count = state.shadow_count(&var_ref.name);
let shadow_name = format!("{}#{}", var_ref.name, shadow_count + 1);
state.new_name(&shadow_name, block_id, var_ref.line)
} else {
state.new_name(&var_ref.name, block_id, var_ref.line)
}
}
pub fn handle_ownership_move(var_ref: &VarRef, state: &mut RenamingState) {
state.mark_moved(&var_ref.name);
}
pub fn handle_match_binding(
pattern_var: &str,
state: &mut RenamingState,
block_id: usize,
line: u32,
) -> SsaNameId {
state.new_name(pattern_var, block_id, line)
}
impl RenamingState {
pub fn shadow_count(&self, var: &str) -> usize {
let prefix = format!("{}#", var);
self.stacks
.keys()
.filter(|k| k.starts_with(&prefix))
.count()
}
pub fn mark_moved(&mut self, var: &str) {
self.moved_vars.insert(var.to_string());
}
pub fn is_moved(&self, var: &str) -> bool {
self.moved_vars.contains(var)
}
}
pub fn process_var_ref_with_context(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
instructions: &mut Vec<SsaInstruction>,
) -> Option<SsaNameId> {
if is_blank_identifier(&var_ref.name) {
return None;
}
match &var_ref.context {
Some(VarRefContext::AugmentedAssignment) => {
let result = handle_augmented_assignment(var_ref, state, block_id);
instructions.extend(result.instructions);
result.definitions.first().copied()
}
Some(VarRefContext::WalrusOperator) => {
let result = handle_walrus_operator(var_ref, state, block_id);
instructions.extend(result.instructions);
result.definitions.first().copied()
}
Some(VarRefContext::ComprehensionScope) => {
let scoped_name = format!("{}$comprehension", var_ref.name);
match var_ref.ref_type {
RefType::Definition | RefType::Update => {
let target = state.new_name(&scoped_name, block_id, var_ref.line);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: Vec::new(),
line: var_ref.line,
source_text: None,
});
Some(target)
}
RefType::Use => state.current(&scoped_name),
}
}
Some(VarRefContext::Shadowing) => {
Some(handle_rust_shadowing(var_ref, state, block_id, true))
}
Some(VarRefContext::PatternBinding) | Some(VarRefContext::MatchArmBinding) => Some(
handle_match_binding(&var_ref.name, state, block_id, var_ref.line),
),
Some(VarRefContext::OwnershipMove) => {
handle_ownership_move(var_ref, state);
state.current(&var_ref.name)
}
Some(VarRefContext::BlankIdentifier) => {
None
}
Some(VarRefContext::GlobalNonlocal) => {
None
}
Some(VarRefContext::MultipleAssignment)
| Some(VarRefContext::Destructuring)
| Some(VarRefContext::MultipleReturn)
| Some(VarRefContext::ShortDeclaration) => {
process_normal_var_ref(var_ref, state, block_id, instructions)
}
Some(VarRefContext::OptionalChain) => {
process_normal_var_ref(var_ref, state, block_id, instructions)
}
Some(VarRefContext::ClosureCapture) => {
state.current(&var_ref.name)
}
Some(VarRefContext::DeferCapture) => {
state.current(&var_ref.name)
}
Some(VarRefContext::MatchBinding) => {
Some(state.new_name(&var_ref.name, block_id, var_ref.line))
}
None => {
process_normal_var_ref(var_ref, state, block_id, instructions)
}
}
}
fn process_normal_var_ref(
var_ref: &VarRef,
state: &mut RenamingState,
block_id: usize,
instructions: &mut Vec<SsaInstruction>,
) -> Option<SsaNameId> {
match var_ref.ref_type {
RefType::Definition => {
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: Vec::new(),
line: var_ref.line,
source_text: None,
});
Some(target)
}
RefType::Update => {
let use_version = state.current(&var_ref.name);
let target = state.new_name(&var_ref.name, block_id, var_ref.line);
instructions.push(SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(target),
uses: use_version.into_iter().collect(),
line: var_ref.line,
source_text: None,
});
Some(target)
}
RefType::Use => {
state.current(&var_ref.name)
}
}
}
pub fn process_statement_group(
refs: &[&VarRef],
state: &mut RenamingState,
block_id: usize,
) -> LanguageConstructResult {
let context = refs.iter().find_map(|r| r.context.as_ref());
match context {
Some(VarRefContext::MultipleAssignment) => {
handle_multiple_assignment(refs, state, block_id)
}
Some(VarRefContext::Destructuring) => handle_destructuring(refs, state, block_id),
Some(VarRefContext::MultipleReturn) | Some(VarRefContext::ShortDeclaration) => {
handle_multiple_return(refs, state, block_id)
}
_ => {
let mut definitions = Vec::new();
let mut uses = Vec::new();
let mut instructions = Vec::new();
for var_ref in refs {
if let Some(id) =
process_normal_var_ref(var_ref, state, block_id, &mut instructions)
{
match var_ref.ref_type {
RefType::Definition | RefType::Update => definitions.push(id),
RefType::Use => uses.push(id),
}
}
}
LanguageConstructResult {
definitions,
uses,
instructions,
}
}
}
}