Skip to content

Commit e578beb

Browse files
authored
Keep PHI nodes internally, but stop exposing PHI-vs-op complexity to … (#12)
* Keep PHI nodes internally, but stop exposing PHI-vs-op complexity to most passes remove doc * Fix post-rebase conflict cleanup for SSA PHI abstraction
1 parent c17767d commit e578beb

File tree

8 files changed

+545
-193
lines changed

8 files changed

+545
-193
lines changed

crates/r2dec/src/analysis/use_info.rs

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -507,46 +507,27 @@ fn coalesce_variables(scratch: &mut UseScratch, blocks: &[SSABlock], env: &PassE
507507
let mut reg_versions: HashMap<String, Vec<(String, u32)>> = HashMap::new();
508508

509509
for block in blocks {
510-
for op in &block.ops {
511-
if let Some(dst) = op.dst() {
512-
if !is_register_candidate_var(dst, env) {
513-
continue;
514-
}
515-
let base = dst.name.to_ascii_lowercase();
516-
reg_versions
517-
.entry(base)
518-
.or_default()
519-
.push((dst.display_name(), dst.version));
520-
}
521-
for src in op.sources() {
522-
if !is_register_candidate_var(src, env) {
523-
continue;
524-
}
525-
let base = src.name.to_ascii_lowercase();
526-
reg_versions
527-
.entry(base)
528-
.or_default()
529-
.push((src.display_name(), src.version));
510+
block.for_each_def(|def| {
511+
if !is_register_candidate_var(def.var, env) {
512+
return;
530513
}
531-
}
532-
for phi in &block.phis {
533-
if is_register_candidate_var(&phi.dst, env) {
534-
let base = phi.dst.name.to_ascii_lowercase();
535-
reg_versions
536-
.entry(base)
537-
.or_default()
538-
.push((phi.dst.display_name(), phi.dst.version));
539-
}
540-
for (_, src) in &phi.sources {
541-
if is_register_candidate_var(src, env) {
542-
let base = src.name.to_ascii_lowercase();
543-
reg_versions
544-
.entry(base)
545-
.or_default()
546-
.push((src.display_name(), src.version));
547-
}
514+
let base = def.var.name.to_ascii_lowercase();
515+
reg_versions
516+
.entry(base)
517+
.or_default()
518+
.push((def.var.display_name(), def.var.version));
519+
});
520+
521+
block.for_each_source(|src| {
522+
if !is_register_candidate_var(src.var, env) {
523+
return;
548524
}
549-
}
525+
let base = src.var.name.to_ascii_lowercase();
526+
reg_versions
527+
.entry(base)
528+
.or_default()
529+
.push((src.var.display_name(), src.var.version));
530+
});
550531
}
551532

552533
let mut uf_parent: HashMap<String, String> = HashMap::new();

crates/r2ssa/src/function.rs

Lines changed: 249 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ pub struct PhiNode {
6363
pub sources: Vec<(u64, SSAVar)>, // (predecessor addr, variable)
6464
}
6565

66+
/// Location metadata for a source variable use.
67+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68+
pub enum SourceSite {
69+
/// Source from a phi node input.
70+
Phi {
71+
phi_idx: usize,
72+
src_idx: usize,
73+
pred_addr: u64,
74+
},
75+
/// Source from a regular SSA operation input.
76+
Op { op_idx: usize, src_idx: usize },
77+
}
78+
79+
/// A source variable with its location metadata.
80+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81+
pub struct SourceRef<'a> {
82+
pub var: &'a SSAVar,
83+
pub site: SourceSite,
84+
}
85+
86+
/// Location metadata for a destination variable definition.
87+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88+
pub enum DefSite {
89+
/// Destination written by a phi node.
90+
Phi { phi_idx: usize },
91+
/// Destination written by a regular operation.
92+
Op { op_idx: usize },
93+
}
94+
95+
/// A destination variable with its definition site metadata.
96+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97+
pub struct DefRef<'a> {
98+
pub var: &'a SSAVar,
99+
pub site: DefSite,
100+
}
101+
66102
impl SSAFunction {
67103
/// Build an SSA function from a sequence of r2il blocks.
68104
pub fn from_blocks(blocks: &[R2ILBlock]) -> Option<Self> {
@@ -367,28 +403,37 @@ impl SSAFunction {
367403
let mut uses = Vec::new();
368404

369405
for (&addr, block) in &self.blocks {
370-
// Check phi nodes
371-
for (phi_idx, phi) in block.phis.iter().enumerate() {
372-
for (src_idx, (_, src)) in phi.sources.iter().enumerate() {
373-
if src == var {
374-
uses.push((addr, UseLocation::Phi { phi_idx, src_idx }));
375-
}
406+
block.for_each_source(|src| {
407+
if src.var != var {
408+
return;
376409
}
377-
}
378-
379-
// Check operations
380-
for (op_idx, op) in block.ops.iter().enumerate() {
381-
for (src_idx, src) in op.sources().iter().enumerate() {
382-
if *src == var {
383-
uses.push((addr, UseLocation::Op { op_idx, src_idx }));
384-
}
385-
}
386-
}
410+
let use_loc = match src.site {
411+
SourceSite::Phi {
412+
phi_idx, src_idx, ..
413+
} => UseLocation::Phi { phi_idx, src_idx },
414+
SourceSite::Op { op_idx, src_idx } => UseLocation::Op { op_idx, src_idx },
415+
};
416+
uses.push((addr, use_loc));
417+
});
387418
}
388419

389420
uses
390421
}
391422

423+
/// Iterate over all source uses in all blocks.
424+
pub fn for_each_source<F: FnMut(u64, SourceRef<'_>)>(&self, mut f: F) {
425+
for block in self.blocks() {
426+
block.for_each_source(|src| f(block.addr, src));
427+
}
428+
}
429+
430+
/// Iterate over all definitions in all blocks.
431+
pub fn for_each_def<F: FnMut(u64, DefRef<'_>)>(&self, mut f: F) {
432+
for block in self.blocks() {
433+
block.for_each_def(|def| f(block.addr, def));
434+
}
435+
}
436+
392437
/// Compute a backward slice for a sink variable.
393438
pub fn backward_slice(&self, sink: &SSAVar) -> BackwardSlice {
394439
backward_slice_from_var(self, sink)
@@ -501,6 +546,61 @@ pub enum UseLocation {
501546
}
502547

503548
impl SSABlock {
549+
/// Visit all phi source variables in deterministic index order.
550+
pub fn for_each_phi_source<F: FnMut(SourceRef<'_>)>(&self, mut f: F) {
551+
for (phi_idx, phi) in self.phis.iter().enumerate() {
552+
for (src_idx, (pred_addr, src)) in phi.sources.iter().enumerate() {
553+
f(SourceRef {
554+
var: src,
555+
site: SourceSite::Phi {
556+
phi_idx,
557+
src_idx,
558+
pred_addr: *pred_addr,
559+
},
560+
});
561+
}
562+
}
563+
}
564+
565+
/// Visit all operation source variables in deterministic index order.
566+
pub fn for_each_op_source<F: FnMut(SourceRef<'_>)>(&self, mut f: F) {
567+
for (op_idx, op) in self.ops.iter().enumerate() {
568+
let mut src_idx = 0usize;
569+
op.for_each_source(|src| {
570+
f(SourceRef {
571+
var: src,
572+
site: SourceSite::Op { op_idx, src_idx },
573+
});
574+
src_idx += 1;
575+
});
576+
}
577+
}
578+
579+
/// Visit all source variables (phis first, then ops) in index order.
580+
pub fn for_each_source<F: FnMut(SourceRef<'_>)>(&self, mut f: F) {
581+
self.for_each_phi_source(&mut f);
582+
self.for_each_op_source(f);
583+
}
584+
585+
/// Visit all destination definitions (phis first, then ops) in index order.
586+
pub fn for_each_def<F: FnMut(DefRef<'_>)>(&self, mut f: F) {
587+
for (phi_idx, phi) in self.phis.iter().enumerate() {
588+
f(DefRef {
589+
var: &phi.dst,
590+
site: DefSite::Phi { phi_idx },
591+
});
592+
}
593+
594+
for (op_idx, op) in self.ops.iter().enumerate() {
595+
if let Some(dst) = op.dst() {
596+
f(DefRef {
597+
var: dst,
598+
site: DefSite::Op { op_idx },
599+
});
600+
}
601+
}
602+
}
603+
504604
/// Get all operations including phi nodes (as SSAOp::Phi).
505605
pub fn all_ops(&self) -> impl Iterator<Item = SSAOp> + '_ {
506606
let phi_ops = self.phis.iter().map(|phi| SSAOp::Phi {
@@ -825,4 +925,137 @@ mod tests {
825925
assert!(func.get_block(0x1004).is_none());
826926
assert_eq!(func.idom(0x1008), Some(0x1000));
827927
}
928+
929+
#[test]
930+
fn test_for_each_source_reports_phi_and_op_sites() {
931+
let blocks = vec![
932+
R2ILBlock {
933+
addr: 0x1000,
934+
size: 4,
935+
ops: vec![R2ILOp::CBranch {
936+
target: make_const(0x1008, 8),
937+
cond: make_const(1, 1),
938+
}],
939+
op_metadata: std::collections::BTreeMap::new(),
940+
switch_info: None,
941+
},
942+
R2ILBlock {
943+
addr: 0x1004,
944+
size: 4,
945+
ops: vec![
946+
R2ILOp::Copy {
947+
dst: make_reg(0, 8),
948+
src: make_const(1, 8),
949+
},
950+
R2ILOp::Branch {
951+
target: make_const(0x100c, 8),
952+
},
953+
],
954+
op_metadata: std::collections::BTreeMap::new(),
955+
switch_info: None,
956+
},
957+
R2ILBlock {
958+
addr: 0x1008,
959+
size: 4,
960+
ops: vec![
961+
R2ILOp::Copy {
962+
dst: make_reg(0, 8),
963+
src: make_const(2, 8),
964+
},
965+
R2ILOp::Branch {
966+
target: make_const(0x100c, 8),
967+
},
968+
],
969+
op_metadata: std::collections::BTreeMap::new(),
970+
switch_info: None,
971+
},
972+
R2ILBlock {
973+
addr: 0x100c,
974+
size: 4,
975+
ops: vec![R2ILOp::IntAdd {
976+
dst: make_reg(8, 8),
977+
a: make_reg(0, 8),
978+
b: make_const(3, 8),
979+
}],
980+
op_metadata: std::collections::BTreeMap::new(),
981+
switch_info: None,
982+
},
983+
];
984+
985+
let func = SSAFunction::from_blocks_raw_no_arch(&blocks).expect("raw SSA should build");
986+
let merge = func.get_block(0x100c).expect("merge block");
987+
assert!(merge.has_phis(), "fixture should produce a merge phi");
988+
989+
let mut seen = Vec::new();
990+
merge.for_each_source(|src| {
991+
seen.push(match src.site {
992+
SourceSite::Phi {
993+
phi_idx,
994+
src_idx,
995+
pred_addr,
996+
} => format!(
997+
"phi:{}:{}:0x{:x}:{}",
998+
phi_idx,
999+
src_idx,
1000+
pred_addr,
1001+
src.var.display_name()
1002+
),
1003+
SourceSite::Op { op_idx, src_idx } => {
1004+
format!("op:{}:{}:{}", op_idx, src_idx, src.var.display_name())
1005+
}
1006+
});
1007+
});
1008+
1009+
assert_eq!(seen.len(), 4, "2 phi sources + 2 IntAdd sources expected");
1010+
assert!(
1011+
seen[0].starts_with("phi:0:0:"),
1012+
"first source should be first phi input"
1013+
);
1014+
assert!(
1015+
seen[1].starts_with("phi:0:1:"),
1016+
"second source should be second phi input"
1017+
);
1018+
assert!(
1019+
seen[2].starts_with("op:0:0:"),
1020+
"third source should be first op input"
1021+
);
1022+
assert!(
1023+
seen[3].starts_with("op:0:1:"),
1024+
"fourth source should be second op input"
1025+
);
1026+
}
1027+
1028+
#[test]
1029+
fn test_for_each_def_reports_phi_and_op_defs() {
1030+
let block = SSABlock {
1031+
addr: 0x2000,
1032+
size: 4,
1033+
phis: vec![PhiNode {
1034+
dst: SSAVar::new("reg:0", 2, 8),
1035+
sources: vec![(0x1000, SSAVar::new("reg:0", 0, 8))],
1036+
}],
1037+
ops: vec![
1038+
SSAOp::Copy {
1039+
dst: SSAVar::new("reg:8", 1, 8),
1040+
src: SSAVar::new("reg:0", 2, 8),
1041+
},
1042+
SSAOp::Return {
1043+
target: SSAVar::new("reg:8", 1, 8),
1044+
},
1045+
],
1046+
};
1047+
1048+
let mut seen = Vec::new();
1049+
block.for_each_def(|def| {
1050+
seen.push(match def.site {
1051+
DefSite::Phi { phi_idx } => format!("phi:{}:{}", phi_idx, def.var.display_name()),
1052+
DefSite::Op { op_idx } => format!("op:{}:{}", op_idx, def.var.display_name()),
1053+
});
1054+
});
1055+
1056+
assert_eq!(
1057+
seen,
1058+
vec!["phi:0:reg:0_2".to_string(), "op:0:reg:8_1".to_string()]
1059+
);
1060+
}
8281061
}

crates/r2ssa/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ pub use cfg::{BasicBlock, BlockTerminator, CFG, CFGEdge};
3434
pub use defuse::{
3535
BackwardSlice, DefUseInfo, SliceOpRef, backward_slice_from_op, backward_slice_from_var, def_use,
3636
};
37-
pub use function::{PhiNode, SSABlock as FunctionSSABlock, SSAFunction, SwitchInfo};
37+
pub use function::{
38+
DefRef, DefSite, PhiNode, SSABlock as FunctionSSABlock, SSAFunction, SourceRef, SourceSite,
39+
SwitchInfo,
40+
};
3841
pub use op::SSAOp;
3942
pub use optimize::{OptimizationConfig, OptimizationStats, optimize_function};
4043
pub use taint::{DefaultTaintPolicy, TaintAnalysis, TaintLabel, TaintPolicy, TaintResult};

0 commit comments

Comments
 (0)