mod framework;
mod lattice;
mod liveness;
mod reaching;
mod sccp;
mod solver;
pub use framework::{AnalysisResults, DataFlowAnalysis, DataFlowCfg, Direction};
pub use lattice::{JoinSemiLattice, Lattice, MeetSemiLattice};
pub use liveness::{LiveVariables, LivenessResult};
pub use reaching::{ReachingDefinitions, ReachingDefsResult};
pub use sccp::{ConstantPropagation, ScalarValue, SccpResult};
pub use solver::DataFlowSolver;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
analysis::{cfg::ControlFlowGraph, ssa::SsaConverter},
assembly::{decode_blocks, InstructionAssembler},
metadata::typesystem::PointerSize,
};
fn build_cfg(assembler: InstructionAssembler) -> ControlFlowGraph<'static> {
let (bytecode, _max_stack, _) = assembler.finish().expect("Failed to assemble bytecode");
let blocks =
decode_blocks(&bytecode, 0, 0x1000, Some(bytecode.len())).expect("Failed to decode");
ControlFlowGraph::from_basic_blocks(blocks).expect("Failed to build CFG")
}
fn build_ssa(
assembler: InstructionAssembler,
num_args: usize,
num_locals: usize,
) -> (crate::analysis::SsaFunction, ControlFlowGraph<'static>) {
let cfg = build_cfg(assembler);
let ssa =
SsaConverter::build(&cfg, num_args, num_locals, None).expect("SSA construction failed");
(ssa, cfg)
}
#[test]
fn test_reaching_defs_simple_function() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0()
.unwrap()
.ldarg_1()
.unwrap()
.add()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 2, 0);
let analysis = ReachingDefinitions::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
if let Some(out_state) = results.out_state(0) {
assert!(out_state.count() >= 2);
}
}
#[test]
fn test_reaching_defs_with_local_assignment() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0()
.unwrap()
.stloc_0()
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 1, 1);
let analysis = ReachingDefinitions::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
if let Some(out_state) = results.out_state(0) {
assert!(out_state.count() >= 2);
}
}
#[test]
fn test_reaching_defs_diamond_cfg() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0()
.unwrap()
.brfalse_s("else")
.unwrap()
.ldc_i4_1()
.unwrap()
.stloc_0()
.unwrap()
.br_s("merge")
.unwrap()
.label("else")
.unwrap()
.ldc_i4_2()
.unwrap()
.stloc_0()
.unwrap()
.label("merge")
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 1, 1);
let analysis = ReachingDefinitions::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
let merge_block = ssa.block_count() - 1;
if let Some(in_state) = results.in_state(merge_block) {
assert!(in_state.count() >= 1);
}
}
#[test]
fn test_liveness_simple_function() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0()
.unwrap()
.ldarg_1()
.unwrap()
.add()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 2, 0);
let analysis = LiveVariables::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
assert!(results.in_state(0).is_some());
assert!(results.out_state(0).is_some());
if let Some(in_state) = results.in_state(0) {
let _ = in_state.count();
}
}
#[test]
fn test_liveness_dead_local() {
let mut asm = InstructionAssembler::new();
asm.ldc_i4_1()
.unwrap()
.stloc_0()
.unwrap()
.ldarg_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 1, 1);
let analysis = LiveVariables::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let _results = solver.solve(&ssa, &cfg);
}
#[test]
fn test_liveness_loop() {
let mut asm = InstructionAssembler::new();
asm.ldc_i4_0()
.unwrap()
.stloc_0()
.unwrap()
.label("loop")
.unwrap()
.ldloc_0()
.unwrap()
.ldc_i4_s(10)
.unwrap()
.bge_s("exit")
.unwrap()
.ldloc_0()
.unwrap()
.ldc_i4_1()
.unwrap()
.add()
.unwrap()
.stloc_0()
.unwrap()
.br_s("loop")
.unwrap()
.label("exit")
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 0, 1);
let analysis = LiveVariables::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
let mut found_live = false;
for block_idx in 0..ssa.block_count() {
if let Some(in_state) = results.in_state(block_idx) {
if in_state.count() > 0 {
found_live = true;
break;
}
}
}
assert!(found_live, "Expected some variables to be live in loop");
}
#[test]
fn test_sccp_constant_folding() {
let mut asm = InstructionAssembler::new();
asm.ldc_i4_1()
.unwrap()
.ldc_i4_2()
.unwrap()
.add()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 0, 0);
let mut sccp = ConstantPropagation::new(PointerSize::Bit64);
let results = sccp.analyze(&ssa, &cfg);
assert!(results.is_block_executable(0));
assert!(results.constant_count() >= 2); }
#[test]
fn test_sccp_unreachable_code() {
let mut asm = InstructionAssembler::new();
asm.ldc_i4_0()
.unwrap() .brtrue_s("then")
.unwrap()
.ldc_i4_2()
.unwrap()
.stloc_0()
.unwrap()
.br_s("merge")
.unwrap()
.label("then")
.unwrap()
.ldc_i4_1()
.unwrap()
.stloc_0()
.unwrap()
.label("merge")
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 0, 1);
let mut sccp = ConstantPropagation::new(PointerSize::Bit64);
let results = sccp.analyze(&ssa, &cfg);
assert!(results.is_block_executable(0));
assert!(results.executable_block_count() >= 2);
}
#[test]
fn test_sccp_phi_with_constants() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0()
.unwrap()
.brfalse_s("else")
.unwrap()
.ldc_i4_1()
.unwrap()
.stloc_0()
.unwrap()
.br_s("merge")
.unwrap()
.label("else")
.unwrap()
.ldc_i4_1()
.unwrap() .stloc_0()
.unwrap()
.label("merge")
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 1, 1);
let mut sccp = ConstantPropagation::new(PointerSize::Bit64);
let results = sccp.analyze(&ssa, &cfg);
assert!(results.executable_block_count() >= 3);
}
#[test]
fn test_solver_convergence() {
let mut asm = InstructionAssembler::new();
asm.ldc_i4_0()
.unwrap()
.stloc_0()
.unwrap()
.ldc_i4_0()
.unwrap()
.stloc_1()
.unwrap()
.label("outer")
.unwrap()
.ldloc_0()
.unwrap()
.ldc_i4_s(5)
.unwrap()
.bge_s("exit")
.unwrap()
.label("inner")
.unwrap()
.ldloc_1()
.unwrap()
.ldc_i4_s(5)
.unwrap()
.bge_s("outer_inc")
.unwrap()
.ldloc_1()
.unwrap()
.ldc_i4_1()
.unwrap()
.add()
.unwrap()
.stloc_1()
.unwrap()
.br_s("inner")
.unwrap()
.label("outer_inc")
.unwrap()
.ldloc_0()
.unwrap()
.ldc_i4_1()
.unwrap()
.add()
.unwrap()
.stloc_0()
.unwrap()
.ldc_i4_0()
.unwrap()
.stloc_1()
.unwrap()
.br_s("outer")
.unwrap()
.label("exit")
.unwrap()
.ldloc_0()
.unwrap()
.ret()
.unwrap();
let (ssa, cfg) = build_ssa(asm, 0, 2);
let rd_analysis = ReachingDefinitions::new(&ssa);
let rd_solver = DataFlowSolver::new(rd_analysis);
let _rd_results = rd_solver.solve(&ssa, &cfg);
let lv_analysis = LiveVariables::new(&ssa);
let lv_solver = DataFlowSolver::new(lv_analysis);
let _lv_results = lv_solver.solve(&ssa, &cfg);
}
#[test]
fn test_analysis_results_access() {
let mut asm = InstructionAssembler::new();
asm.ldarg_0().unwrap().ret().unwrap();
let (ssa, cfg) = build_ssa(asm, 1, 0);
let analysis = ReachingDefinitions::new(&ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(&ssa, &cfg);
assert!(results.in_state(0).is_some());
assert!(results.out_state(0).is_some());
assert!(results.in_state(999).is_none());
assert!(results.out_state(999).is_none());
}
}