use std::collections::{HashMap, HashSet, VecDeque};
use wide::u64x4;
use crate::dfg::types::DFGInfo;
#[derive(Debug, Clone)]
pub struct SliceCriteria {
pub line: usize,
pub variable: Option<String>,
}
impl SliceCriteria {
#[inline]
pub fn at_line(line: usize) -> Self {
Self {
line,
variable: None,
}
}
#[inline]
pub fn at_line_variable(line: usize, variable: impl Into<String>) -> Self {
Self {
line,
variable: Some(variable.into()),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SliceResult {
pub function_name: String,
pub target_line: usize,
pub direction: String,
pub lines: Vec<usize>,
pub variables: Vec<String>,
pub variable_filter: Option<String>,
pub metrics: SliceMetrics,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SliceMetrics {
pub slice_size: usize,
pub edges_traversed: usize,
pub slice_ratio: f64,
pub variable_count: usize,
}
impl SliceMetrics {
#[allow(dead_code)] #[inline]
pub fn empty() -> Self {
Self {
slice_size: 0,
edges_traversed: 0,
slice_ratio: 0.0,
variable_count: 0,
}
}
}
impl SliceResult {
#[allow(dead_code)] #[inline]
pub fn contains_line(&self, line: usize) -> bool {
self.lines.binary_search(&line).is_ok()
}
#[allow(dead_code)] pub fn line_range(&self) -> Option<(usize, usize)> {
match (self.lines.first(), self.lines.last()) {
(Some(&first), Some(&last)) => Some((first, last)),
_ => None,
}
}
#[allow(dead_code)] #[inline]
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
pub fn backward_slice(dfg: &DFGInfo, criteria: &SliceCriteria) -> SliceResult {
let lines = if let Some(ref var) = criteria.variable {
backward_slice_variable_impl(dfg, criteria.line, var)
} else {
backward_slice_all(dfg, criteria.line)
};
let variables = collect_slice_variables(dfg, &lines);
let function_line_range = compute_function_line_range(dfg);
let total_lines = function_line_range
.map(|(min, max)| max.saturating_sub(min).saturating_add(1))
.unwrap_or(1);
let edges_traversed = count_edges_in_slice(dfg, &lines);
let metrics = SliceMetrics {
slice_size: lines.len(),
edges_traversed,
slice_ratio: lines.len() as f64 / total_lines as f64,
variable_count: variables.len(),
};
SliceResult {
function_name: dfg.function_name.clone(),
target_line: criteria.line,
direction: "backward".to_string(),
lines,
variables,
variable_filter: criteria.variable.clone(),
metrics,
}
}
pub fn forward_slice(dfg: &DFGInfo, criteria: &SliceCriteria) -> SliceResult {
let lines = if let Some(ref var) = criteria.variable {
forward_slice_variable_impl(dfg, criteria.line, var)
} else {
forward_slice_all(dfg, criteria.line)
};
let variables = collect_slice_variables(dfg, &lines);
let function_line_range = compute_function_line_range(dfg);
let total_lines = function_line_range
.map(|(min, max)| max.saturating_sub(min).saturating_add(1))
.unwrap_or(1);
let edges_traversed = count_edges_in_slice(dfg, &lines);
let metrics = SliceMetrics {
slice_size: lines.len(),
edges_traversed,
slice_ratio: lines.len() as f64 / total_lines as f64,
variable_count: variables.len(),
};
SliceResult {
function_name: dfg.function_name.clone(),
target_line: criteria.line,
direction: "forward".to_string(),
lines,
variables,
variable_filter: criteria.variable.clone(),
metrics,
}
}
#[allow(dead_code)] pub fn backward_slice_variable(dfg: &DFGInfo, line: usize, variable: &str) -> Vec<usize> {
backward_slice_variable_impl(dfg, line, variable)
}
#[allow(dead_code)] pub fn forward_slice_variable(dfg: &DFGInfo, line: usize, variable: &str) -> Vec<usize> {
forward_slice_variable_impl(dfg, line, variable)
}
#[allow(dead_code)] pub fn bidirectional_slice(dfg: &DFGInfo, criteria: &SliceCriteria) -> (SliceResult, SliceResult) {
let backward = backward_slice(dfg, criteria);
let forward = forward_slice(dfg, criteria);
(backward, forward)
}
#[allow(dead_code)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChopResult {
pub lines: Vec<usize>,
pub source_line: usize,
pub target_line: usize,
pub variable: Option<String>,
}
impl ChopResult {
#[allow(dead_code)] #[inline]
pub fn contains_line(&self, line: usize) -> bool {
self.lines.binary_search(&line).is_ok()
}
#[allow(dead_code)] pub fn line_range(&self) -> Option<(usize, usize)> {
match (self.lines.first(), self.lines.last()) {
(Some(&first), Some(&last)) => Some((first, last)),
_ => None,
}
}
#[allow(dead_code)] #[inline]
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
#[allow(dead_code)] pub fn chop(dfg: &DFGInfo, source_line: usize, target_line: usize) -> ChopResult {
chop_impl(dfg, source_line, target_line, None)
}
#[allow(dead_code)] pub fn chop_for_variable(
dfg: &DFGInfo,
source_line: usize,
target_line: usize,
variable: &str,
) -> ChopResult {
chop_impl(dfg, source_line, target_line, Some(variable))
}
fn chop_impl(
dfg: &DFGInfo,
source_line: usize,
target_line: usize,
variable: Option<&str>,
) -> ChopResult {
if variable.is_none() {
let lines = chop_bidirectional(dfg, source_line, target_line);
return ChopResult {
lines,
source_line,
target_line,
variable: None,
};
}
let var = match variable {
Some(v) => v,
None => {
return ChopResult {
lines: Vec::new(),
source_line,
target_line,
variable: None,
};
}
};
let cache = dfg.get_adjacency_cache();
let source_exists = cache.incoming.contains_key(&source_line)
|| cache.outgoing.contains_key(&source_line);
let target_exists = cache.incoming.contains_key(&target_line)
|| cache.outgoing.contains_key(&target_line);
if !source_exists || !target_exists {
return ChopResult {
lines: Vec::new(),
source_line,
target_line,
variable: Some(var.to_string()),
};
}
let forward_set: HashSet<usize> = forward_slice_variable_impl(dfg, source_line, var)
.into_iter()
.collect();
let backward_set: HashSet<usize> = backward_slice_variable_impl(dfg, target_line, var)
.into_iter()
.collect();
let mut chop_lines: Vec<usize> = forward_set
.intersection(&backward_set)
.copied()
.collect();
chop_lines.sort_unstable();
ChopResult {
lines: chop_lines,
source_line,
target_line,
variable: Some(var.to_string()),
}
}
fn chop_bidirectional(dfg: &DFGInfo, source_line: usize, target_line: usize) -> Vec<usize> {
if source_line == target_line {
let cache = dfg.get_adjacency_cache();
let exists = cache.incoming.contains_key(&source_line)
|| cache.outgoing.contains_key(&source_line);
return if exists { vec![source_line] } else { Vec::new() };
}
let cache = dfg.get_adjacency_cache();
let source_exists = cache.incoming.contains_key(&source_line)
|| cache.outgoing.contains_key(&source_line);
let target_exists = cache.incoming.contains_key(&target_line)
|| cache.outgoing.contains_key(&target_line);
if !source_exists || !target_exists {
return Vec::new();
}
let estimated_size = (cache.outgoing.len() + cache.incoming.len()) / 4;
let mut forward_visited: HashSet<usize> = HashSet::with_capacity(estimated_size);
let mut backward_visited: HashSet<usize> = HashSet::with_capacity(estimated_size);
let mut forward_frontier: VecDeque<usize> = VecDeque::with_capacity(estimated_size / 2);
let mut backward_frontier: VecDeque<usize> = VecDeque::with_capacity(estimated_size / 2);
forward_frontier.push_back(source_line);
forward_visited.insert(source_line);
backward_frontier.push_back(target_line);
backward_visited.insert(target_line);
while !forward_frontier.is_empty() || !backward_frontier.is_empty() {
if let Some(line) = forward_frontier.pop_front() {
if let Some(successors) = cache.outgoing.get(&line) {
for &succ in successors {
if forward_visited.insert(succ) {
forward_frontier.push_back(succ);
}
}
}
}
if let Some(line) = backward_frontier.pop_front() {
if let Some(predecessors) = cache.incoming.get(&line) {
for &pred in predecessors {
if backward_visited.insert(pred) {
backward_frontier.push_back(pred);
}
}
}
}
}
let mut result: Vec<usize> = if forward_visited.len() <= backward_visited.len() {
forward_visited
.iter()
.filter(|&line| backward_visited.contains(line))
.copied()
.collect()
} else {
backward_visited
.iter()
.filter(|&line| forward_visited.contains(line))
.copied()
.collect()
};
result.sort_unstable();
result
}
pub(crate) fn backward_slice_all(dfg: &DFGInfo, target_line: usize) -> Vec<usize> {
if dfg.edges.is_empty() {
return Vec::new();
}
let cache = dfg.get_adjacency_cache();
let target_exists = cache.incoming.contains_key(&target_line)
|| cache.outgoing.contains_key(&target_line);
if !target_exists {
return Vec::new();
}
let mut result = HashSet::new();
let mut frontier = VecDeque::new();
frontier.push_back(target_line);
while let Some(line) = frontier.pop_front() {
if result.insert(line) {
if let Some(sources) = cache.incoming.get(&line) {
for &source in sources {
if !result.contains(&source) {
frontier.push_back(source);
}
}
}
}
}
let mut lines: Vec<_> = result.into_iter().collect();
lines.sort_unstable();
lines
}
fn backward_slice_variable_impl(dfg: &DFGInfo, target_line: usize, variable: &str) -> Vec<usize> {
backward_slice_variable_with_control_impl(dfg, target_line, variable, None)
}
fn backward_slice_variable_with_control_impl(
dfg: &DFGInfo,
target_line: usize,
variable: &str,
control_deps: Option<&HashMap<usize, Vec<usize>>>,
) -> Vec<usize> {
let cache = dfg.get_adjacency_cache();
let target_exists = cache.incoming.contains_key(&target_line)
|| cache.outgoing.contains_key(&target_line);
if !target_exists {
return Vec::new();
}
let var_incoming = dfg.get_var_incoming_lines(variable);
let mut result = HashSet::new();
let mut frontier = VecDeque::new();
frontier.push_back(target_line);
while let Some(line) = frontier.pop_front() {
if result.insert(line) {
if let Some(var_adj) = var_incoming {
if let Some(sources) = var_adj.get(&line) {
for &source in sources {
if !result.contains(&source) {
frontier.push_back(source);
}
}
}
}
if let Some(deps) = control_deps {
if let Some(condition_lines) = deps.get(&line) {
for &condition_line in condition_lines {
if !result.contains(&condition_line) {
frontier.push_back(condition_line);
}
}
}
}
}
}
let mut lines: Vec<_> = result.into_iter().collect();
lines.sort_unstable();
lines
}
pub(crate) fn forward_slice_all(dfg: &DFGInfo, source_line: usize) -> Vec<usize> {
if dfg.edges.is_empty() {
return Vec::new();
}
let cache = dfg.get_adjacency_cache();
let source_exists = cache.incoming.contains_key(&source_line)
|| cache.outgoing.contains_key(&source_line);
if !source_exists {
return Vec::new();
}
let mut result = HashSet::new();
let mut frontier = VecDeque::new();
frontier.push_back(source_line);
while let Some(line) = frontier.pop_front() {
if result.insert(line) {
if let Some(targets) = cache.outgoing.get(&line) {
for &target in targets {
if !result.contains(&target) {
frontier.push_back(target);
}
}
}
}
}
let mut lines: Vec<_> = result.into_iter().collect();
lines.sort_unstable();
lines
}
fn forward_slice_variable_impl(dfg: &DFGInfo, source_line: usize, variable: &str) -> Vec<usize> {
forward_slice_variable_with_control_impl(dfg, source_line, variable, None)
}
fn forward_slice_variable_with_control_impl(
dfg: &DFGInfo,
source_line: usize,
variable: &str,
control_deps: Option<&HashMap<usize, Vec<usize>>>,
) -> Vec<usize> {
let cache = dfg.get_adjacency_cache();
let source_exists = cache.incoming.contains_key(&source_line)
|| cache.outgoing.contains_key(&source_line);
if !source_exists {
return Vec::new();
}
let var_outgoing = dfg.get_var_outgoing_lines(variable);
let mut result = HashSet::new();
let mut frontier = VecDeque::new();
frontier.push_back(source_line);
while let Some(line) = frontier.pop_front() {
if result.insert(line) {
if let Some(var_adj) = var_outgoing {
if let Some(targets) = var_adj.get(&line) {
for &target in targets {
if !result.contains(&target) {
frontier.push_back(target);
}
}
}
}
if let Some(deps) = control_deps {
if let Some(dependent_lines) = deps.get(&line) {
for &dependent_line in dependent_lines {
if !result.contains(&dependent_line) {
frontier.push_back(dependent_line);
}
}
}
}
}
}
let mut lines: Vec<_> = result.into_iter().collect();
lines.sort_unstable();
lines
}
fn collect_slice_variables(dfg: &DFGInfo, lines: &[usize]) -> Vec<String> {
let line_set: HashSet<usize> = lines.iter().copied().collect();
let mut variables: HashSet<&str> = HashSet::new();
for edge in &dfg.edges {
if line_set.contains(&edge.from_line) || line_set.contains(&edge.to_line) {
variables.insert(&edge.variable);
}
}
let mut result: Vec<String> = variables.into_iter().map(String::from).collect();
result.sort_unstable();
result
}
fn count_edges_in_slice(dfg: &DFGInfo, lines: &[usize]) -> usize {
if lines.is_empty() {
return 0;
}
const LINEAR_THRESHOLD: usize = 16;
let contains = if lines.len() <= LINEAR_THRESHOLD {
|lines: &[usize], value: usize| -> bool { lines.contains(&value) }
} else {
|lines: &[usize], value: usize| -> bool { lines.binary_search(&value).is_ok() }
};
dfg.edges
.iter()
.filter(|e| {
e.from_line != e.to_line
&& contains(lines, e.from_line)
&& contains(lines, e.to_line)
})
.count()
}
fn compute_function_line_range(dfg: &DFGInfo) -> Option<(usize, usize)> {
if dfg.edges.is_empty() {
return None;
}
let edges = &dfg.edges;
let edge_count = edges.len();
const SIMD_THRESHOLD: usize = 8;
if edge_count < SIMD_THRESHOLD {
let mut min_line = usize::MAX;
let mut max_line = 0;
for edge in edges {
min_line = min_line.min(edge.from_line).min(edge.to_line);
max_line = max_line.max(edge.from_line).max(edge.to_line);
}
return if min_line <= max_line {
Some((min_line, max_line))
} else {
None
};
}
let total_values = edge_count * 2;
let mut line_values: Vec<u64> = Vec::with_capacity(total_values);
for edge in edges {
line_values.push(edge.from_line as u64);
line_values.push(edge.to_line as u64);
}
let mut simd_min = u64x4::splat(u64::MAX);
let mut simd_max = u64x4::splat(0);
let simd_chunks = total_values / 4;
let simd_end = simd_chunks * 4;
for chunk_idx in 0..simd_chunks {
let base = chunk_idx * 4;
let values = u64x4::new([
line_values[base],
line_values[base + 1],
line_values[base + 2],
line_values[base + 3],
]);
let lt_mask = values.cmp_lt(simd_min);
simd_min = lt_mask.blend(values, simd_min);
let gt_mask = values.cmp_gt(simd_max);
simd_max = gt_mask.blend(values, simd_max);
}
let min_arr = simd_min.to_array();
let max_arr = simd_max.to_array();
let mut min_line = min_arr[0].min(min_arr[1]).min(min_arr[2]).min(min_arr[3]) as usize;
let mut max_line = max_arr[0].max(max_arr[1]).max(max_arr[2]).max(max_arr[3]) as usize;
for &value in &line_values[simd_end..] {
let v = value as usize;
min_line = min_line.min(v);
max_line = max_line.max(v);
}
if min_line <= max_line {
Some((min_line, max_line))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dfg::types::{DFGInfo, DataflowEdge, DataflowKind};
fn create_test_dfg() -> DFGInfo {
DFGInfo::new(
"test_func".to_string(),
vec![
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 2,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 4,
kind: DataflowKind::Use,
},
DataflowEdge {
variable: "y".to_string(),
from_line: 2,
to_line: 3,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "z".to_string(),
from_line: 3,
to_line: 4,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "w".to_string(),
from_line: 4,
to_line: 5,
kind: DataflowKind::Definition,
},
],
[
("x".to_string(), vec![1]),
("y".to_string(), vec![2]),
("z".to_string(), vec![3]),
("w".to_string(), vec![4]),
]
.into_iter()
.collect(),
[
("x".to_string(), vec![2, 4]),
("y".to_string(), vec![3]),
("z".to_string(), vec![4]),
("w".to_string(), vec![5]),
]
.into_iter()
.collect(),
)
}
#[test]
fn test_backward_slice_from_return() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(5);
let result = backward_slice(&dfg, &criteria);
assert_eq!(result.lines, vec![1, 2, 3, 4, 5]);
assert_eq!(result.direction, "backward");
assert_eq!(result.target_line, 5);
}
#[test]
fn test_backward_slice_from_middle() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(3);
let result = backward_slice(&dfg, &criteria);
assert_eq!(result.lines, vec![1, 2, 3]);
}
#[test]
fn test_forward_slice_from_start() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(1);
let result = forward_slice(&dfg, &criteria);
assert_eq!(result.lines, vec![1, 2, 3, 4, 5]);
assert_eq!(result.direction, "forward");
}
#[test]
fn test_forward_slice_from_middle() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(3);
let result = forward_slice(&dfg, &criteria);
assert_eq!(result.lines, vec![3, 4, 5]);
}
#[test]
fn test_backward_slice_variable_specific() {
let dfg = create_test_dfg();
let result = backward_slice_variable(&dfg, 4, "x");
assert_eq!(result, vec![1, 4]);
}
#[test]
fn test_forward_slice_variable_specific() {
let dfg = create_test_dfg();
let result = forward_slice_variable(&dfg, 2, "y");
assert_eq!(result, vec![2, 3]);
}
#[test]
fn test_chop() {
let dfg = create_test_dfg();
let result = chop(&dfg, 1, 5);
assert_eq!(result.lines, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_chop_narrower() {
let dfg = create_test_dfg();
let result = chop(&dfg, 2, 4);
assert_eq!(result.lines, vec![2, 3, 4]);
}
#[test]
fn test_chop_for_variable() {
let dfg = create_test_dfg();
let result_x = chop_for_variable(&dfg, 1, 4, "x");
assert_eq!(result_x.lines, vec![1, 4]);
assert_eq!(result_x.variable, Some("x".to_string()));
assert_eq!(result_x.source_line, 1);
assert_eq!(result_x.target_line, 4);
}
#[test]
fn test_chop_for_variable_y() {
let dfg = create_test_dfg();
let result = chop_for_variable(&dfg, 2, 3, "y");
assert_eq!(result.lines, vec![2, 3]);
assert_eq!(result.source_line, 2);
assert_eq!(result.target_line, 3);
assert_eq!(result.variable, Some("y".to_string()));
}
#[test]
fn test_chop_for_variable_no_path() {
let dfg = create_test_dfg();
let result = chop_for_variable(&dfg, 1, 5, "x");
assert!(result.is_empty());
assert_eq!(result.variable, Some("x".to_string()));
}
#[test]
fn test_chop_result_methods() {
let dfg = create_test_dfg();
let result = chop(&dfg, 2, 4);
assert!(result.contains_line(2));
assert!(result.contains_line(3));
assert!(result.contains_line(4));
assert!(!result.contains_line(1));
assert!(!result.contains_line(5));
assert_eq!(result.line_range(), Some((2, 4)));
assert!(!result.is_empty());
}
#[test]
fn test_chop_metadata() {
let dfg = create_test_dfg();
let result = chop(&dfg, 1, 5);
assert_eq!(result.source_line, 1);
assert_eq!(result.target_line, 5);
assert!(result.variable.is_none());
}
#[test]
fn test_bidirectional_slice() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(3);
let (backward, forward) = bidirectional_slice(&dfg, &criteria);
assert_eq!(backward.lines, vec![1, 2, 3]);
assert_eq!(forward.lines, vec![3, 4, 5]);
}
#[test]
fn test_slice_metrics() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(5);
let result = backward_slice(&dfg, &criteria);
assert_eq!(result.metrics.slice_size, 5);
assert!(result.metrics.slice_ratio > 0.0);
assert!(result.metrics.slice_ratio <= 1.0);
}
#[test]
fn test_slice_result_contains_line() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(3);
let result = backward_slice(&dfg, &criteria);
assert!(result.contains_line(1));
assert!(result.contains_line(2));
assert!(result.contains_line(3));
assert!(!result.contains_line(4));
assert!(!result.contains_line(5));
}
#[test]
fn test_slice_result_line_range() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(3);
let result = backward_slice(&dfg, &criteria);
assert_eq!(result.line_range(), Some((1, 3)));
}
#[test]
fn test_empty_dfg_slice() {
let dfg = DFGInfo::new(
"empty".to_string(),
vec![],
Default::default(),
Default::default(),
);
let criteria = SliceCriteria::at_line(1);
let result = backward_slice(&dfg, &criteria);
assert!(result.lines.is_empty());
assert!(result.variables.is_empty());
}
#[test]
fn test_slice_nonexistent_line() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(100);
let result = backward_slice(&dfg, &criteria);
assert!(result.lines.is_empty());
assert!(result.variables.is_empty());
}
#[test]
fn test_forward_slice_nonexistent_line() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line(100);
let result = forward_slice(&dfg, &criteria);
assert!(result.lines.is_empty());
}
#[test]
fn test_chop_nonexistent_source() {
let dfg = create_test_dfg();
let result = chop(&dfg, 100, 3);
assert!(result.is_empty());
}
#[test]
fn test_chop_nonexistent_target() {
let dfg = create_test_dfg();
let result = chop(&dfg, 1, 100);
assert!(result.is_empty());
}
#[test]
fn test_variable_slice_nonexistent_line() {
let dfg = create_test_dfg();
let result = backward_slice_variable(&dfg, 100, "x");
assert!(result.is_empty());
}
#[test]
fn test_slice_with_variable_filter_criteria() {
let dfg = create_test_dfg();
let criteria = SliceCriteria::at_line_variable(4, "x");
let result = backward_slice(&dfg, &criteria);
assert_eq!(result.lines, vec![1, 4]);
assert_eq!(result.variable_filter, Some("x".to_string()));
}
#[test]
fn test_empty_dfg_returns_empty_slice_backward() {
let empty_dfg = DFGInfo::new(
"empty_test".to_string(),
vec![],
Default::default(),
Default::default(),
);
let result = backward_slice_all(&empty_dfg, 42);
assert!(
result.is_empty(),
"Empty DFG should return empty slice, got {:?}",
result
);
}
#[test]
fn test_empty_dfg_returns_empty_slice_forward() {
let empty_dfg = DFGInfo::new(
"empty_test".to_string(),
vec![],
Default::default(),
Default::default(),
);
let result = forward_slice_all(&empty_dfg, 42);
assert!(
result.is_empty(),
"Empty DFG should return empty slice, got {:?}",
result
);
}
#[test]
fn test_nonexistent_target_returns_empty_slice_backward() {
let dfg = DFGInfo::new(
"test".to_string(),
vec![DataflowEdge {
from_line: 1,
to_line: 2,
variable: "x".into(),
kind: DataflowKind::Definition,
}],
[("x".to_string(), vec![1])].into_iter().collect(),
[("x".to_string(), vec![2])].into_iter().collect(),
);
let result = backward_slice_all(&dfg, 100);
assert!(
result.is_empty(),
"Nonexistent target should return empty slice, got {:?}",
result
);
}
#[test]
fn test_nonexistent_source_returns_empty_slice_forward() {
let dfg = DFGInfo::new(
"test".to_string(),
vec![DataflowEdge {
from_line: 1,
to_line: 2,
variable: "x".into(),
kind: DataflowKind::Definition,
}],
[("x".to_string(), vec![1])].into_iter().collect(),
[("x".to_string(), vec![2])].into_iter().collect(),
);
let result = forward_slice_all(&dfg, 100);
assert!(
result.is_empty(),
"Nonexistent source should return empty slice, got {:?}",
result
);
}
#[test]
fn test_slice_metrics_empty() {
let metrics = SliceMetrics::empty();
assert_eq!(metrics.slice_size, 0);
assert_eq!(metrics.edges_traversed, 0);
assert!((metrics.slice_ratio - 0.0).abs() < f64::EPSILON);
assert_eq!(metrics.variable_count, 0);
}
#[test]
fn test_empty_dfg_slice_preserves_target_line() {
let empty_dfg = DFGInfo::new(
"empty".to_string(),
vec![],
Default::default(),
Default::default(),
);
let criteria = SliceCriteria::at_line(42);
let result = backward_slice(&empty_dfg, &criteria);
assert!(result.lines.is_empty());
assert_eq!(result.target_line, 42);
assert_eq!(result.direction, "backward");
}
#[test]
fn test_chop_bidirectional_same_source_target() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 2, 2);
assert_eq!(result, vec![2]);
}
#[test]
fn test_chop_bidirectional_same_source_target_nonexistent() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 100, 100);
assert!(result.is_empty());
}
#[test]
fn test_chop_bidirectional_full_path() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 1, 5);
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_chop_bidirectional_partial_path() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 2, 4);
assert_eq!(result, vec![2, 3, 4]);
}
#[test]
fn test_chop_bidirectional_nonexistent_source() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 100, 5);
assert!(result.is_empty());
}
#[test]
fn test_chop_bidirectional_nonexistent_target() {
let dfg = create_test_dfg();
let result = chop_bidirectional(&dfg, 1, 100);
assert!(result.is_empty());
}
#[test]
fn test_chop_bidirectional_matches_original() {
let dfg = create_test_dfg();
for source in 1..=5 {
for target in 1..=5 {
let bidirectional = chop_bidirectional(&dfg, source, target);
let forward_set: HashSet<usize> = forward_slice_all(&dfg, source)
.into_iter()
.collect();
let backward_set: HashSet<usize> = backward_slice_all(&dfg, target)
.into_iter()
.collect();
let mut original: Vec<usize> = forward_set
.intersection(&backward_set)
.copied()
.collect();
original.sort_unstable();
assert_eq!(
bidirectional, original,
"Mismatch for chop({}, {}): bidirectional={:?}, original={:?}",
source, target, bidirectional, original
);
}
}
}
#[test]
fn test_chop_bidirectional_with_branching_graph() {
let dfg = DFGInfo::new(
"branching".to_string(),
vec![
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 2,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "y".to_string(),
from_line: 1,
to_line: 3,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "x".to_string(),
from_line: 2,
to_line: 4,
kind: DataflowKind::Use,
},
DataflowEdge {
variable: "y".to_string(),
from_line: 3,
to_line: 4,
kind: DataflowKind::Use,
},
],
[
("x".to_string(), vec![1]),
("y".to_string(), vec![1]),
]
.into_iter()
.collect(),
[
("x".to_string(), vec![2, 4]),
("y".to_string(), vec![3, 4]),
]
.into_iter()
.collect(),
);
let result = chop_bidirectional(&dfg, 1, 4);
assert_eq!(result, vec![1, 2, 3, 4],
"Bidirectional chop should find ALL nodes on ALL paths");
}
#[test]
fn test_chop_bidirectional_no_path() {
let dfg = DFGInfo::new(
"disconnected".to_string(),
vec![
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 2,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "y".to_string(),
from_line: 3,
to_line: 4,
kind: DataflowKind::Definition,
},
],
[
("x".to_string(), vec![1]),
("y".to_string(), vec![3]),
]
.into_iter()
.collect(),
[
("x".to_string(), vec![2]),
("y".to_string(), vec![4]),
]
.into_iter()
.collect(),
);
let result = chop_bidirectional(&dfg, 1, 4);
assert!(result.is_empty(), "Should return empty when no path exists");
}
#[test]
fn test_chop_uses_bidirectional_internally() {
let dfg = create_test_dfg();
let chop_result = chop(&dfg, 2, 4);
let bidirectional_result = chop_bidirectional(&dfg, 2, 4);
assert_eq!(chop_result.lines, bidirectional_result,
"chop() should use chop_bidirectional internally");
}
#[test]
fn test_chop_empty_dfg() {
let empty_dfg = DFGInfo::new(
"empty".to_string(),
vec![],
Default::default(),
Default::default(),
);
let result = chop_bidirectional(&empty_dfg, 1, 5);
assert!(result.is_empty());
}
#[test]
fn test_compute_line_range_empty() {
let dfg = DFGInfo::new(
"empty".to_string(),
vec![],
Default::default(),
Default::default(),
);
assert_eq!(compute_function_line_range(&dfg), None);
}
#[test]
fn test_compute_line_range_scalar_path() {
let dfg = create_test_dfg();
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((1, 5)));
}
#[test]
fn test_compute_line_range_simd_path() {
let mut edges = Vec::with_capacity(20);
for i in 0..20 {
edges.push(DataflowEdge {
variable: format!("v{}", i),
from_line: 10 + i,
to_line: 100 + i,
kind: DataflowKind::Definition,
});
}
let dfg = DFGInfo::new(
"simd_test".to_string(),
edges,
Default::default(),
Default::default(),
);
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((10, 119)));
}
#[test]
fn test_compute_line_range_simd_with_remainder() {
let mut edges = Vec::with_capacity(10);
for i in 0..10 {
edges.push(DataflowEdge {
variable: format!("v{}", i),
from_line: 5 + i * 2,
to_line: 6 + i * 2,
kind: DataflowKind::Definition,
});
}
let dfg = DFGInfo::new(
"simd_remainder_test".to_string(),
edges,
Default::default(),
Default::default(),
);
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((5, 24)));
}
#[test]
fn test_compute_line_range_simd_odd_remainder() {
let mut edges = Vec::with_capacity(9);
for i in 0..9 {
edges.push(DataflowEdge {
variable: format!("v{}", i),
from_line: 1000 - i * 10,
to_line: 2000 + i * 5,
kind: DataflowKind::Definition,
});
}
let dfg = DFGInfo::new(
"simd_odd_test".to_string(),
edges,
Default::default(),
Default::default(),
);
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((920, 2040)));
}
#[test]
fn test_compute_line_range_single_edge() {
let dfg = DFGInfo::new(
"single".to_string(),
vec![DataflowEdge {
variable: "x".to_string(),
from_line: 42,
to_line: 100,
kind: DataflowKind::Definition,
}],
Default::default(),
Default::default(),
);
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((42, 100)));
}
#[test]
fn test_compute_line_range_same_from_to() {
let mut edges = Vec::with_capacity(10);
for i in 0..10 {
edges.push(DataflowEdge {
variable: format!("v{}", i),
from_line: 50,
to_line: 50,
kind: DataflowKind::Definition,
});
}
let dfg = DFGInfo::new(
"same_line".to_string(),
edges,
Default::default(),
Default::default(),
);
let result = compute_function_line_range(&dfg);
assert_eq!(result, Some((50, 50)));
}
}