@@ -202,6 +202,7 @@ impl InterferenceGraph {
202202 cfg : & CFG ,
203203 semantic : & SemanticNode ,
204204 liveness : & SsaLiveness ,
205+ required_phis : & BTreeSet < SsaVar > ,
205206 ) -> Result < Self , SourceVariableError > {
206207 let mut graph = Self :: default ( ) ;
207208 if let Some ( inputs) = liveness. live_in . get ( & cfg. entry ) {
@@ -249,7 +250,9 @@ impl InterferenceGraph {
249250 return Err ( SourceVariableError :: LivenessMismatch ( block) ) ;
250251 }
251252 }
252- SemanticLiveness :: analyze ( semantic, & liveness. retained ) ?. add_interference ( & mut graph) ;
253+ let semantic_liveness = SemanticLiveness :: analyze ( semantic, & liveness. retained ) ?;
254+ semantic_liveness. add_recovered_phi_interference ( cfg, required_phis, & mut graph) ;
255+ semantic_liveness. add_interference ( & mut graph) ;
253256 Ok ( graph)
254257 }
255258
@@ -324,6 +327,7 @@ impl InterferenceGraph {
324327struct SemanticSiteFacts {
325328 uses : BTreeSet < SsaVar > ,
326329 definitions : BTreeSet < SsaVar > ,
330+ blocks : BTreeSet < BlockId > ,
327331}
328332
329333struct SemanticLiveness {
@@ -400,6 +404,60 @@ impl SemanticLiveness {
400404 }
401405 }
402406 }
407+
408+ fn add_recovered_phi_interference (
409+ & self ,
410+ cfg : & CFG ,
411+ required_phis : & BTreeSet < SsaVar > ,
412+ graph : & mut InterferenceGraph ,
413+ ) {
414+ let mut uses_by_block = BTreeMap :: < BlockId , BTreeSet < SsaVar > > :: new ( ) ;
415+ for facts in self . sites . values ( ) {
416+ for block in & facts. blocks {
417+ uses_by_block
418+ . entry ( * block)
419+ . or_default ( )
420+ . extend ( facts. uses . iter ( ) . copied ( ) ) ;
421+ }
422+ }
423+
424+ for ( block, uses) in uses_by_block {
425+ let Some ( body) = cfg. block ( block) else {
426+ continue ;
427+ } ;
428+ for phi in body
429+ . insns
430+ . iter ( )
431+ . filter ( |instruction| instruction. insn_type == InsnType :: Phi )
432+ {
433+ let Some ( result) = phi. result . as_ref ( ) . and_then ( SsaVar :: from_reg) else {
434+ continue ;
435+ } ;
436+ if !required_phis. contains ( & result) {
437+ continue ;
438+ }
439+ let inputs = phi
440+ . args
441+ . iter ( )
442+ . filter_map ( InsnArg :: as_register)
443+ . filter_map ( SsaVar :: from_reg)
444+ . collect :: < BTreeSet < _ > > ( ) ;
445+ // Value recovery can synthesize a use of a pre-Phi SSA value
446+ // in a statement originating in the Phi block. The CFG never
447+ // contained that use, so physical liveness alone would allow
448+ // the Phi result to share its source variable and its edge
449+ // copy would overwrite the old value before the recovered
450+ // expression reads it.
451+ for usage in uses
452+ . intersection ( & inputs)
453+ . copied ( )
454+ . filter ( |usage| * usage != result)
455+ {
456+ graph. add ( result, usage) ;
457+ }
458+ }
459+ }
460+ }
403461}
404462
405463struct SemanticSiteCollector < ' a > {
@@ -517,6 +575,9 @@ impl<'a> SemanticSiteCollector<'a> {
517575 . site
518576 . ok_or ( SourceVariableError :: MissingSemanticSite ( "statement" ) ) ?;
519577 self . site ( site) ;
578+ if let Some ( origin) = & statement. origin {
579+ self . site ( site) . blocks . insert ( origin. block ) ;
580+ }
520581 match & statement. kind {
521582 SemanticStatementKind :: Instruction ( operation) => {
522583 self . add_uses (
@@ -679,7 +740,7 @@ impl InstructionVisitor for InstructionUses {
679740#[ cfg( test) ]
680741mod tests {
681742 use super :: { InterferenceGraph , SsaLiveness } ;
682- use crate :: ir:: { analysis:: SsaVar , ArgType , Block , IfOp , InsnArg , InsnNode , CFG } ;
743+ use crate :: ir:: { analysis:: SsaVar , ArgType , Block , IfOp , InsnArg , InsnNode , RegisterArg , CFG } ;
683744 use std:: collections:: BTreeSet ;
684745
685746 #[ test]
@@ -698,12 +759,87 @@ mod tests {
698759
699760 let retained = BTreeSet :: from ( [ left, right] ) ;
700761 let liveness = SsaLiveness :: analyze ( & cfg, & retained) . unwrap ( ) ;
701- let interference =
702- InterferenceGraph :: build ( & cfg, & crate :: ir:: SemanticNode :: Empty , & liveness) . unwrap ( ) ;
762+ let interference = InterferenceGraph :: build (
763+ & cfg,
764+ & crate :: ir:: SemanticNode :: Empty ,
765+ & liveness,
766+ & BTreeSet :: new ( ) ,
767+ )
768+ . unwrap ( ) ;
703769
704770 assert ! ( interference
705771 . hard_edges
706772 . get( & left)
707773 . is_some_and( |neighbors| neighbors. contains( & right) ) ) ;
708774 }
775+
776+ #[ test]
777+ fn recovered_same_block_use_interferes_with_required_phi_result ( ) {
778+ use crate :: ir:: {
779+ BlockId , InstructionId , SemanticBlock , SemanticNode , SemanticSiteNumbering ,
780+ SemanticStatement , StatementOrigin ,
781+ } ;
782+
783+ let predecessor = BlockId :: new ( 0 ) ;
784+ let join = BlockId :: new ( 1 ) ;
785+ let old = RegisterArg :: new_ssa ( 0 , 0 , ArgType :: INT ) ;
786+ let unrelated = RegisterArg :: new_ssa ( 2 , 0 , ArgType :: INT ) ;
787+ let phi_result = RegisterArg :: new_ssa ( 0 , 1 , ArgType :: INT ) ;
788+ let old_value = SsaVar :: from_reg ( & old) . unwrap ( ) ;
789+ let unrelated_value = SsaVar :: from_reg ( & unrelated) . unwrap ( ) ;
790+ let phi_value = SsaVar :: from_reg ( & phi_result) . unwrap ( ) ;
791+
792+ let mut predecessor_block = Block :: new ( predecessor) ;
793+ predecessor_block. push ( InsnNode :: const_value ( old. clone ( ) , 0 ) ) ;
794+ predecessor_block. push ( InsnNode :: const_value ( unrelated. clone ( ) , 1 ) ) ;
795+ let mut join_block = Block :: new ( join) ;
796+ join_block. push ( InsnNode :: phi (
797+ phi_result,
798+ vec ! [ ( predecessor. raw( ) , InsnArg :: Reg ( old. clone( ) ) ) ] ,
799+ ) ) ;
800+ let mut cfg = CFG :: new ( "recovered-phi-use" ) ;
801+ cfg. entry = predecessor;
802+ cfg. add_block ( predecessor_block) ;
803+ cfg. add_block ( join_block) ;
804+ cfg. add_edge ( predecessor, join, crate :: ir:: EdgeKind :: Normal ) ;
805+
806+ let mut statement = SemanticStatement :: instruction ( InsnNode :: mov (
807+ RegisterArg :: new_ssa ( 1 , 0 , ArgType :: INT ) ,
808+ InsnArg :: Reg ( old) ,
809+ ) )
810+ . unwrap ( ) ;
811+ statement. origin = Some ( StatementOrigin {
812+ block : join,
813+ instruction : InstructionId :: new ( 0 ) ,
814+ } ) ;
815+ let mut unrelated_statement = SemanticStatement :: instruction ( InsnNode :: mov (
816+ RegisterArg :: new_ssa ( 3 , 0 , ArgType :: INT ) ,
817+ InsnArg :: Reg ( unrelated) ,
818+ ) )
819+ . unwrap ( ) ;
820+ unrelated_statement. origin = Some ( StatementOrigin {
821+ block : join,
822+ instruction : InstructionId :: new ( 0 ) ,
823+ } ) ;
824+ let mut semantic = SemanticNode :: BasicBlock ( SemanticBlock {
825+ id : join,
826+ statements : vec ! [ statement, unrelated_statement] ,
827+ } ) ;
828+ SemanticSiteNumbering :: assign ( & mut semantic) . unwrap ( ) ;
829+
830+ let retained = BTreeSet :: from ( [ old_value, unrelated_value, phi_value] ) ;
831+ let liveness = SsaLiveness :: analyze ( & cfg, & retained) . unwrap ( ) ;
832+ let interference =
833+ InterferenceGraph :: build ( & cfg, & semantic, & liveness, & BTreeSet :: from ( [ phi_value] ) )
834+ . unwrap ( ) ;
835+
836+ assert ! ( interference
837+ . hard_edges
838+ . get( & phi_value)
839+ . is_some_and( |neighbors| neighbors. contains( & old_value) ) ) ;
840+ assert ! ( !interference
841+ . hard_edges
842+ . get( & phi_value)
843+ . is_some_and( |neighbors| neighbors. contains( & unrelated_value) ) ) ;
844+ }
709845}
0 commit comments