@@ -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+
66102impl 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
503548impl 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}
0 commit comments