use std::collections::HashMap;
use crate::analysis::ssa::{SsaVarId, VariableOrigin};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StackSlotSource {
Defined {
instruction_idx: usize,
},
Inherited,
}
#[derive(Debug, Clone, Copy)]
pub struct StackSlot {
pub var: SsaVarId,
pub source: StackSlotSource,
pub address_target: Option<VariableOrigin>,
}
#[derive(Debug, Clone)]
pub struct SimulationResult {
pub uses: Vec<SsaVarId>,
pub def: Option<SsaVarId>,
pub store_target: Option<VariableOrigin>,
}
impl SimulationResult {
#[must_use]
pub fn empty() -> Self {
Self {
uses: Vec::new(),
def: None,
store_target: None,
}
}
#[must_use]
pub fn def_only(var: SsaVarId) -> Self {
Self {
uses: Vec::new(),
def: Some(var),
store_target: None,
}
}
#[must_use]
pub fn with_def(uses: Vec<SsaVarId>, def: SsaVarId) -> Self {
Self {
uses,
def: Some(def),
store_target: None,
}
}
#[must_use]
pub fn uses_only(uses: Vec<SsaVarId>) -> Self {
Self {
uses,
def: None,
store_target: None,
}
}
#[must_use]
pub fn indirect_store(uses: Vec<SsaVarId>, target: VariableOrigin) -> Self {
Self {
uses,
def: None,
store_target: Some(target),
}
}
}
#[derive(Debug, Clone)]
struct VariableState {
current_var: SsaVarId,
version: u32,
address_taken: bool,
}
impl VariableState {
fn new(initial_var: SsaVarId) -> Self {
Self {
current_var: initial_var,
version: 0,
address_taken: false,
}
}
}
#[derive(Debug)]
pub struct StackSimulator {
stack: Vec<StackSlot>,
args: Vec<VariableState>,
locals: Vec<VariableState>,
next_stack_slot: u32,
num_args: usize,
num_locals: usize,
load_origins: HashMap<SsaVarId, VariableOrigin>,
current_instruction: usize,
}
impl StackSimulator {
#[must_use]
pub fn new(num_args: usize, num_locals: usize) -> Self {
let mut args = Vec::with_capacity(num_args);
for _ in 0..num_args {
args.push(VariableState::new(SsaVarId::new()));
}
let mut locals = Vec::with_capacity(num_locals);
for _ in 0..num_locals {
locals.push(VariableState::new(SsaVarId::new()));
}
Self {
stack: Vec::with_capacity(16),
args,
locals,
next_stack_slot: 0,
num_args,
num_locals,
load_origins: HashMap::new(),
current_instruction: 0,
}
}
pub fn set_instruction_index(&mut self, idx: usize) {
self.current_instruction = idx;
}
#[must_use]
pub fn stack_depth(&self) -> usize {
self.stack.len()
}
#[must_use]
pub fn is_stack_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn initialize_stack(&mut self, depth: usize) {
self.stack.clear();
self.current_instruction = 0;
for _ in 0..depth {
let (var, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var,
source: StackSlotSource::Inherited,
address_target: None,
});
}
}
pub fn reset_stack_to_depth(&mut self, depth: usize) {
self.stack.clear();
self.current_instruction = 0;
for _ in 0..depth {
let (var, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var,
source: StackSlotSource::Inherited,
address_target: None,
});
}
}
#[must_use]
pub fn num_args(&self) -> usize {
self.num_args
}
#[must_use]
pub fn num_locals(&self) -> usize {
self.num_locals
}
#[must_use]
pub fn is_arg_address_taken(&self, index: usize) -> bool {
self.args.get(index).is_some_and(|s| s.address_taken)
}
#[must_use]
pub fn is_local_address_taken(&self, index: usize) -> bool {
self.locals.get(index).is_some_and(|s| s.address_taken)
}
#[must_use]
pub fn load_origins(&self) -> &HashMap<SsaVarId, VariableOrigin> {
&self.load_origins
}
#[must_use]
pub fn stack_snapshot(&self) -> Vec<SsaVarId> {
self.stack.iter().map(|slot| slot.var).collect()
}
#[must_use]
pub fn stack_snapshot_enhanced(&self) -> Vec<StackSlot> {
self.stack.clone()
}
fn alloc_stack_var(&mut self) -> (SsaVarId, VariableOrigin) {
let var = SsaVarId::new();
let origin = VariableOrigin::Stack(self.next_stack_slot);
self.next_stack_slot += 1;
(var, origin)
}
#[must_use]
pub fn get_arg_var(&self, index: usize) -> Option<SsaVarId> {
self.args.get(index).map(|s| s.current_var)
}
#[must_use]
pub fn get_local_var(&self, index: usize) -> Option<SsaVarId> {
self.locals.get(index).map(|s| s.current_var)
}
pub fn simulate_ldarg(&mut self, index: usize) -> Option<SimulationResult> {
let var = self.get_arg_var(index)?;
let origin = VariableOrigin::Argument(u16::try_from(index).unwrap_or(0));
self.load_origins.insert(var, origin);
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None, });
Some(SimulationResult::empty())
}
pub fn simulate_ldloc(&mut self, index: usize) -> Option<SimulationResult> {
let var = self.get_local_var(index)?;
let origin = VariableOrigin::Local(u16::try_from(index).unwrap_or(0));
self.load_origins.insert(var, origin);
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None, });
Some(SimulationResult::empty())
}
pub fn simulate_starg(&mut self, index: usize) -> Option<SimulationResult> {
let slot = self.stack.pop()?;
if index >= self.args.len() {
self.stack.push(slot);
return None;
}
let new_var = SsaVarId::new();
let state = &mut self.args[index];
state.version += 1;
state.current_var = new_var;
Some(SimulationResult::with_def(vec![slot.var], new_var))
}
pub fn simulate_stloc(&mut self, index: usize) -> Option<SimulationResult> {
let slot = self.stack.pop()?;
if index >= self.locals.len() {
self.stack.push(slot);
return None;
}
let new_var = SsaVarId::new();
let state = &mut self.locals[index];
state.version += 1;
state.current_var = new_var;
Some(SimulationResult::with_def(vec![slot.var], new_var))
}
pub fn simulate_ldarga(&mut self, index: usize) -> Option<SimulationResult> {
let state = self.args.get_mut(index)?;
state.address_taken = true;
let (var, _origin) = self.alloc_stack_var();
#[allow(clippy::cast_possible_truncation)]
let address_target = Some(VariableOrigin::Argument(index as u16));
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target,
});
Some(SimulationResult::def_only(var))
}
pub fn simulate_ldloca(&mut self, index: usize) -> Option<SimulationResult> {
let state = self.locals.get_mut(index)?;
state.address_taken = true;
let (var, _origin) = self.alloc_stack_var();
#[allow(clippy::cast_possible_truncation)]
let address_target = Some(VariableOrigin::Local(index as u16));
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target,
});
Some(SimulationResult::def_only(var))
}
pub fn simulate_push(&mut self) -> (SsaVarId, VariableOrigin) {
let (var, origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None,
});
(var, origin)
}
pub fn simulate_pop(&mut self) -> Option<SsaVarId> {
self.stack.pop().map(|slot| slot.var)
}
pub fn simulate_pop_with_address_target(
&mut self,
) -> Option<(SsaVarId, Option<VariableOrigin>)> {
self.stack.pop().map(|slot| (slot.var, slot.address_target))
}
pub fn simulate_pop_n(&mut self, count: usize) -> Option<Vec<SsaVarId>> {
if self.stack.len() < count {
return None;
}
let mut result = Vec::with_capacity(count);
let start_idx = self.stack.len() - count;
for i in start_idx..self.stack.len() {
result.push(self.stack[i].var);
}
self.stack.truncate(start_idx);
Some(result)
}
pub fn simulate_binary_op(&mut self) -> Option<SimulationResult> {
let uses = self.simulate_pop_n(2)?;
let (def, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var: def,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None,
});
Some(SimulationResult::with_def(uses, def))
}
pub fn simulate_unary_op(&mut self) -> Option<SimulationResult> {
let operand = self.simulate_pop()?;
let (def, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var: def,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None,
});
Some(SimulationResult::with_def(vec![operand], def))
}
pub fn simulate_comparison(&mut self) -> Option<SimulationResult> {
self.simulate_binary_op()
}
pub fn simulate_dup(&mut self) -> Option<SimulationResult> {
let top_slot = self.stack.last()?;
let top = top_slot.var;
let address_target = top_slot.address_target;
let (new_var, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var: new_var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target,
});
Some(SimulationResult::with_def(vec![top], new_var))
}
pub fn simulate_initobj(&mut self) -> Option<SimulationResult> {
let slot = self.stack.pop()?;
let uses = vec![slot.var];
if let Some(target) = slot.address_target {
Some(SimulationResult::indirect_store(uses, target))
} else {
Some(SimulationResult::uses_only(uses))
}
}
pub fn simulate_stind(&mut self) -> Option<SimulationResult> {
let value_slot = self.stack.pop()?;
let addr_slot = self.stack.pop()?;
let uses = vec![addr_slot.var, value_slot.var];
if let Some(target) = addr_slot.address_target {
Some(SimulationResult::indirect_store(uses, target))
} else {
Some(SimulationResult::uses_only(uses))
}
}
#[must_use]
pub fn simulate_ret(&mut self) -> SimulationResult {
let uses = if let Some(slot) = self.stack.pop() {
vec![slot.var]
} else {
vec![]
};
SimulationResult {
uses,
def: None,
store_target: None,
}
}
pub fn simulate_stack_effect(&mut self, pops: u8, pushes: u8) -> Option<SimulationResult> {
let uses = if pops > 0 {
self.simulate_pop_n(pops as usize)?
} else {
Vec::new()
};
let def = if pushes > 0 {
let (var, _origin) = self.alloc_stack_var();
self.stack.push(StackSlot {
var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None,
});
for _ in 1..pushes {
let (extra_var, _) = self.alloc_stack_var();
self.stack.push(StackSlot {
var: extra_var,
source: StackSlotSource::Defined {
instruction_idx: self.current_instruction,
},
address_target: None,
});
}
Some(var)
} else {
None
};
Some(SimulationResult {
uses,
def,
store_target: None,
})
}
pub fn clear_stack(&mut self) {
self.stack.clear();
}
pub fn simulate_leave(&mut self) -> SimulationResult {
let uses: Vec<SsaVarId> = self.stack.iter().map(|slot| slot.var).collect();
self.stack.clear();
SimulationResult::uses_only(uses)
}
pub fn set_stack(&mut self, stack: Vec<SsaVarId>) {
self.stack = stack
.into_iter()
.map(|var| StackSlot {
var,
source: StackSlotSource::Inherited,
address_target: None,
})
.collect();
}
pub fn set_stack_enhanced(&mut self, stack: Vec<StackSlot>) {
self.stack = stack;
}
pub fn set_arg_var(&mut self, index: usize, var: SsaVarId) -> bool {
if let Some(state) = self.args.get_mut(index) {
state.current_var = var;
true
} else {
false
}
}
pub fn set_local_var(&mut self, index: usize, var: SsaVarId) -> bool {
if let Some(state) = self.locals.get_mut(index) {
state.current_var = var;
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simulator_creation() {
let sim = StackSimulator::new(2, 3);
assert_eq!(sim.num_args(), 2);
assert_eq!(sim.num_locals(), 3);
assert!(sim.is_stack_empty());
assert_eq!(sim.stack_depth(), 0);
}
#[test]
fn test_initial_arg_vars() {
let sim = StackSimulator::new(3, 0);
let arg0 = sim.get_arg_var(0);
let arg1 = sim.get_arg_var(1);
let arg2 = sim.get_arg_var(2);
assert!(arg0.is_some());
assert!(arg1.is_some());
assert!(arg2.is_some());
assert_ne!(arg0, arg1);
assert_ne!(arg1, arg2);
assert_ne!(arg0, arg2);
assert_eq!(sim.get_arg_var(3), None);
}
#[test]
fn test_initial_local_vars() {
let sim = StackSimulator::new(2, 3);
let arg0 = sim.get_arg_var(0).unwrap();
let arg1 = sim.get_arg_var(1).unwrap();
let loc0 = sim.get_local_var(0).unwrap();
let loc1 = sim.get_local_var(1).unwrap();
let loc2 = sim.get_local_var(2).unwrap();
let all_vars = [arg0, arg1, loc0, loc1, loc2];
for i in 0..all_vars.len() {
for j in (i + 1)..all_vars.len() {
assert_ne!(
all_vars[i], all_vars[j],
"Variables at {} and {} should be distinct",
i, j
);
}
}
assert_eq!(sim.get_local_var(3), None);
}
#[test]
fn test_simulate_ldarg() {
let mut sim = StackSimulator::new(2, 0);
let result = sim.simulate_ldarg(0).unwrap();
assert!(result.uses.is_empty());
assert_eq!(result.def, None); assert_eq!(sim.stack_depth(), 1);
let result = sim.simulate_ldarg(1).unwrap();
assert_eq!(result.def, None); assert_eq!(sim.stack_depth(), 2);
}
#[test]
fn test_simulate_ldloc() {
let mut sim = StackSimulator::new(1, 2);
let result = sim.simulate_ldloc(0).unwrap();
assert!(result.uses.is_empty());
assert_eq!(result.def, None); assert_eq!(sim.stack_depth(), 1);
}
#[test]
fn test_simulate_starg() {
let mut sim = StackSimulator::new(2, 0);
let arg0 = sim.get_arg_var(0).unwrap();
let initial_arg1 = sim.get_arg_var(1).unwrap();
sim.simulate_ldarg(0);
assert_eq!(sim.stack_depth(), 1);
let result = sim.simulate_starg(1).unwrap();
assert_eq!(result.uses.len(), 1);
assert_eq!(result.uses[0], arg0); assert_eq!(sim.stack_depth(), 0);
let new_var = sim.get_arg_var(1).unwrap();
assert_ne!(new_var, initial_arg1);
assert_eq!(result.def, Some(new_var));
}
#[test]
fn test_simulate_stloc() {
let mut sim = StackSimulator::new(1, 1);
let arg0 = sim.get_arg_var(0).unwrap();
let initial_local0 = sim.get_local_var(0).unwrap();
sim.simulate_ldarg(0);
let result = sim.simulate_stloc(0).unwrap();
assert_eq!(result.uses.len(), 1);
assert_eq!(result.uses[0], arg0);
let new_var = sim.get_local_var(0).unwrap();
assert_ne!(new_var, initial_local0);
assert_eq!(result.def, Some(new_var));
}
#[test]
fn test_simulate_binary_op() {
let mut sim = StackSimulator::new(2, 0);
let arg0 = sim.get_arg_var(0).unwrap();
let arg1 = sim.get_arg_var(1).unwrap();
sim.simulate_ldarg(0);
sim.simulate_ldarg(1);
assert_eq!(sim.stack_depth(), 2);
let result = sim.simulate_binary_op().unwrap();
assert_eq!(result.uses.len(), 2);
assert_eq!(result.uses[0], arg0); assert_eq!(result.uses[1], arg1);
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 1);
}
#[test]
fn test_simulate_unary_op() {
let mut sim = StackSimulator::new(1, 0);
let arg0 = sim.get_arg_var(0).unwrap();
sim.simulate_ldarg(0);
assert_eq!(sim.stack_depth(), 1);
let result = sim.simulate_unary_op().unwrap();
assert_eq!(result.uses.len(), 1);
assert_eq!(result.uses[0], arg0);
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 1);
}
#[test]
fn test_simulate_dup() {
let mut sim = StackSimulator::new(1, 0);
sim.simulate_ldarg(0);
assert_eq!(sim.stack_depth(), 1);
let result = sim.simulate_dup().unwrap();
assert_eq!(result.uses.len(), 1);
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 2);
}
#[test]
fn test_simulate_pop_n() {
let mut sim = StackSimulator::new(3, 0);
let arg1 = sim.get_arg_var(1).unwrap();
let arg2 = sim.get_arg_var(2).unwrap();
sim.simulate_ldarg(0);
sim.simulate_ldarg(1);
sim.simulate_ldarg(2);
assert_eq!(sim.stack_depth(), 3);
let popped = sim.simulate_pop_n(2).unwrap();
assert_eq!(popped.len(), 2);
assert_eq!(popped[0], arg1); assert_eq!(popped[1], arg2); assert_eq!(sim.stack_depth(), 1);
}
#[test]
fn test_simulate_pop_n_underflow() {
let mut sim = StackSimulator::new(1, 0);
sim.simulate_ldarg(0);
assert!(sim.simulate_pop_n(2).is_none());
assert_eq!(sim.stack_depth(), 1); }
#[test]
fn test_simulate_ldarga() {
let mut sim = StackSimulator::new(2, 0);
assert!(!sim.is_arg_address_taken(0));
let result = sim.simulate_ldarga(0).unwrap();
assert!(result.uses.is_empty());
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 1);
assert!(sim.is_arg_address_taken(0));
assert!(!sim.is_arg_address_taken(1));
}
#[test]
fn test_simulate_ldloca() {
let mut sim = StackSimulator::new(0, 2);
assert!(!sim.is_local_address_taken(0));
let result = sim.simulate_ldloca(0).unwrap();
assert!(result.uses.is_empty());
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 1);
assert!(sim.is_local_address_taken(0));
assert!(!sim.is_local_address_taken(1));
}
#[test]
fn test_simulate_stack_effect() {
let mut sim = StackSimulator::new(3, 0);
sim.simulate_ldarg(0);
sim.simulate_ldarg(1);
sim.simulate_ldarg(2);
assert_eq!(sim.stack_depth(), 3);
let result = sim.simulate_stack_effect(2, 1).unwrap();
assert_eq!(result.uses.len(), 2);
assert!(result.def.is_some());
assert_eq!(sim.stack_depth(), 2);
let result = sim.simulate_stack_effect(1, 0).unwrap();
assert_eq!(result.uses.len(), 1);
assert!(result.def.is_none());
assert_eq!(sim.stack_depth(), 1);
}
#[test]
fn test_stack_snapshot_and_restore() {
let mut sim = StackSimulator::new(2, 0);
sim.simulate_ldarg(0);
sim.simulate_ldarg(1);
let snapshot = sim.stack_snapshot();
assert_eq!(snapshot.len(), 2);
sim.clear_stack();
assert!(sim.is_stack_empty());
sim.set_stack(snapshot.clone());
assert_eq!(sim.stack_depth(), 2);
assert_eq!(sim.stack_snapshot(), snapshot);
}
#[test]
fn test_set_arg_var() {
let mut sim = StackSimulator::new(2, 0);
let v = SsaVarId::new();
assert!(sim.set_arg_var(0, v));
assert_eq!(sim.get_arg_var(0), Some(v));
assert!(!sim.set_arg_var(5, v)); }
#[test]
fn test_set_local_var() {
let mut sim = StackSimulator::new(0, 2);
let v = SsaVarId::new();
assert!(sim.set_local_var(0, v));
assert_eq!(sim.get_local_var(0), Some(v));
assert!(!sim.set_local_var(5, v)); }
#[test]
fn test_simulation_result_constructors() {
let empty = SimulationResult::empty();
assert!(empty.uses.is_empty());
assert!(empty.def.is_none());
let v5 = SsaVarId::new();
let def_only = SimulationResult::def_only(v5);
assert!(def_only.uses.is_empty());
assert_eq!(def_only.def, Some(v5));
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses_only = SimulationResult::uses_only(vec![v1, v2]);
assert_eq!(uses_only.uses.len(), 2);
assert!(uses_only.def.is_none());
let u1 = SsaVarId::new();
let u2 = SsaVarId::new();
let v3 = SsaVarId::new();
let with_def = SimulationResult::with_def(vec![u1, u2], v3);
assert_eq!(with_def.uses.len(), 2);
assert_eq!(with_def.def, Some(v3));
}
#[test]
fn test_complex_sequence() {
let mut sim = StackSimulator::new(2, 1);
let arg0 = sim.get_arg_var(0).unwrap();
let arg1 = sim.get_arg_var(1).unwrap();
let r1 = sim.simulate_ldarg(0).unwrap();
assert_eq!(r1.def, None);
let r2 = sim.simulate_ldarg(1).unwrap();
assert_eq!(r2.def, None);
let r3 = sim.simulate_binary_op().unwrap();
assert_eq!(r3.uses, vec![arg0, arg1]);
let add_result = r3.def.unwrap();
let r4 = sim.simulate_stloc(0).unwrap();
assert_eq!(r4.uses, vec![add_result]);
assert!(sim.is_stack_empty());
}
}