Skip to content

Commit df11d61

Browse files
committed
Keep PHI nodes internally, but stop exposing PHI-vs-op complexity to most passes
remove doc
1 parent f4a8e52 commit df11d61

File tree

8 files changed

+524
-190
lines changed

8 files changed

+524
-190
lines changed

crates/r2dec/src/analysis/use_info.rs

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

490490
for block in blocks {
491-
for op in &block.ops {
492-
if let Some(dst) = op.dst() {
493-
if !is_register_candidate_var(dst, env) {
494-
continue;
495-
}
496-
let base = dst.name.to_ascii_lowercase();
497-
reg_versions
498-
.entry(base)
499-
.or_default()
500-
.push((dst.display_name(), dst.version));
501-
}
502-
for src in op.sources() {
503-
if !is_register_candidate_var(src, env) {
504-
continue;
505-
}
506-
let base = src.name.to_ascii_lowercase();
507-
reg_versions
508-
.entry(base)
509-
.or_default()
510-
.push((src.display_name(), src.version));
491+
block.for_each_def(|def| {
492+
if !is_register_candidate_var(def.var, env) {
493+
return;
511494
}
512-
}
513-
for phi in &block.phis {
514-
if is_register_candidate_var(&phi.dst, env) {
515-
let base = phi.dst.name.to_ascii_lowercase();
516-
reg_versions
517-
.entry(base)
518-
.or_default()
519-
.push((phi.dst.display_name(), phi.dst.version));
520-
}
521-
for (_, src) in &phi.sources {
522-
if is_register_candidate_var(src, env) {
523-
let base = src.name.to_ascii_lowercase();
524-
reg_versions
525-
.entry(base)
526-
.or_default()
527-
.push((src.display_name(), src.version));
528-
}
495+
let base = def.var.name.to_ascii_lowercase();
496+
reg_versions
497+
.entry(base)
498+
.or_default()
499+
.push((def.var.display_name(), def.var.version));
500+
});
501+
502+
block.for_each_source(|src| {
503+
if !is_register_candidate_var(src.var, env) {
504+
return;
529505
}
530-
}
506+
let base = src.var.name.to_ascii_lowercase();
507+
reg_versions
508+
.entry(base)
509+
.or_default()
510+
.push((src.var.display_name(), src.var.version));
511+
});
531512
}
532513

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

crates/r2ssa/src/function.rs

Lines changed: 245 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 {
@@ -805,4 +905,133 @@ mod tests {
805905
assert!(func.get_block(0x1004).is_none());
806906
assert_eq!(func.idom(0x1008), Some(0x1000));
807907
}
908+
909+
#[test]
910+
fn test_for_each_source_reports_phi_and_op_sites() {
911+
let blocks = vec![
912+
R2ILBlock {
913+
addr: 0x1000,
914+
size: 4,
915+
ops: vec![R2ILOp::CBranch {
916+
target: make_const(0x1008, 8),
917+
cond: make_const(1, 1),
918+
}],
919+
switch_info: None,
920+
},
921+
R2ILBlock {
922+
addr: 0x1004,
923+
size: 4,
924+
ops: vec![
925+
R2ILOp::Copy {
926+
dst: make_reg(0, 8),
927+
src: make_const(1, 8),
928+
},
929+
R2ILOp::Branch {
930+
target: make_const(0x100c, 8),
931+
},
932+
],
933+
switch_info: None,
934+
},
935+
R2ILBlock {
936+
addr: 0x1008,
937+
size: 4,
938+
ops: vec![
939+
R2ILOp::Copy {
940+
dst: make_reg(0, 8),
941+
src: make_const(2, 8),
942+
},
943+
R2ILOp::Branch {
944+
target: make_const(0x100c, 8),
945+
},
946+
],
947+
switch_info: None,
948+
},
949+
R2ILBlock {
950+
addr: 0x100c,
951+
size: 4,
952+
ops: vec![R2ILOp::IntAdd {
953+
dst: make_reg(8, 8),
954+
a: make_reg(0, 8),
955+
b: make_const(3, 8),
956+
}],
957+
switch_info: None,
958+
},
959+
];
960+
961+
let func = SSAFunction::from_blocks_raw_no_arch(&blocks).expect("raw SSA should build");
962+
let merge = func.get_block(0x100c).expect("merge block");
963+
assert!(merge.has_phis(), "fixture should produce a merge phi");
964+
965+
let mut seen = Vec::new();
966+
merge.for_each_source(|src| {
967+
seen.push(match src.site {
968+
SourceSite::Phi {
969+
phi_idx,
970+
src_idx,
971+
pred_addr,
972+
} => format!(
973+
"phi:{}:{}:0x{:x}:{}",
974+
phi_idx,
975+
src_idx,
976+
pred_addr,
977+
src.var.display_name()
978+
),
979+
SourceSite::Op { op_idx, src_idx } => {
980+
format!("op:{}:{}:{}", op_idx, src_idx, src.var.display_name())
981+
}
982+
});
983+
});
984+
985+
assert_eq!(seen.len(), 4, "2 phi sources + 2 IntAdd sources expected");
986+
assert!(
987+
seen[0].starts_with("phi:0:0:"),
988+
"first source should be first phi input"
989+
);
990+
assert!(
991+
seen[1].starts_with("phi:0:1:"),
992+
"second source should be second phi input"
993+
);
994+
assert!(
995+
seen[2].starts_with("op:0:0:"),
996+
"third source should be first op input"
997+
);
998+
assert!(
999+
seen[3].starts_with("op:0:1:"),
1000+
"fourth source should be second op input"
1001+
);
1002+
}
1003+
1004+
#[test]
1005+
fn test_for_each_def_reports_phi_and_op_defs() {
1006+
let block = SSABlock {
1007+
addr: 0x2000,
1008+
size: 4,
1009+
phis: vec![PhiNode {
1010+
dst: SSAVar::new("reg:0", 2, 8),
1011+
sources: vec![(0x1000, SSAVar::new("reg:0", 0, 8))],
1012+
}],
1013+
ops: vec![
1014+
SSAOp::Copy {
1015+
dst: SSAVar::new("reg:8", 1, 8),
1016+
src: SSAVar::new("reg:0", 2, 8),
1017+
},
1018+
SSAOp::Return {
1019+
target: SSAVar::new("reg:8", 1, 8),
1020+
},
1021+
],
1022+
};
1023+
1024+
let mut seen = Vec::new();
1025+
block.for_each_def(|def| {
1026+
seen.push(match def.site {
1027+
DefSite::Phi { phi_idx } => format!("phi:{}:{}", phi_idx, def.var.display_name()),
1028+
DefSite::Op { op_idx } => format!("op:{}:{}", op_idx, def.var.display_name()),
1029+
});
1030+
});
1031+
1032+
assert_eq!(
1033+
seen,
1034+
vec!["phi:0:reg:0_2".to_string(), "op:0:reg:8_1".to_string()]
1035+
);
1036+
}
8081037
}

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)