4545//! spans and regular repeated copies have symbolic nodes, so graph size is
4646//! normally a function of accesses and boundaries rather than declared bit
4747//! width or unpacked-array length.
48- //! 3. Child modules are analyzed before parents. A module summary contains only
49- //! input-to-output feedthrough proven in the child graph. Instance actuals
50- //! project the child's regions back into the parent, preserving bit
51- //! positions, aggregate layout, and Cartesian repetition axes when that
48+ //! 3. Traversal starts at source modules which are not instantiated by another
49+ //! source template. Concrete child modules are analyzed before their parent;
50+ //! the same child graph emits definition-local diagnostics and produces its
51+ //! input-to-output summary, so the standalone template is not rebuilt.
52+ //! Uninstantiated modules are roots and remain independently checked.
53+ //! Instance actuals project child regions back into the parent, preserving
54+ //! bit positions, aggregate layout, and Cartesian repetition axes when that
5255//! mapping is representable.
5356//! 4. Iterative SCC discovery finds cyclic components without using the process
5457//! stack. For each SCC, diagnostics choose a stable source-backed edge and
@@ -456,6 +459,7 @@ enum ComponentSummaryKey {
456459struct CombSummaryCache {
457460 modules : HashMap < ComponentSummaryKey , ModuleCombSummary > ,
458461 procedures : crate :: comb_memory_ssa:: ProcedureSummaryCache ,
462+ analyzed_templates : HashSet < StrId > ,
459463}
460464
461465#[ derive( Clone , Debug , PartialEq , Eq , PartialOrd , Ord ) ]
@@ -527,12 +531,21 @@ fn analyze(ir: &Ir, collect_coverage: bool) -> (CombAnalysisResult, Vec<Analyzer
527531 let order = module_analysis_order ( ir) ;
528532 for & idx in & order {
529533 if let Component :: Module ( module) = & ir. components [ idx] {
534+ // A concrete instance analysis already checked this source
535+ // template and produced the summary its parent consumes. Generic
536+ // specializations are still all visited by
537+ // `ensure_instance_summaries`; this skips only the redundant
538+ // standalone template pass.
539+ if summaries. analyzed_templates . contains ( & module. name ) {
540+ continue ;
541+ }
530542 // Unevaluable generic params do not have a concrete module shape.
531543 if module. suppress_unassigned {
532544 incomplete. push ( IncompleteCombAnalysis {
533545 module : module. name . to_string ( ) ,
534546 reasons : [ IncompleteReason :: UnevaluatedGeneric ] . into ( ) ,
535547 } ) ;
548+ summaries. analyzed_templates . insert ( module. name ) ;
536549 continue ;
537550 }
538551 ensure_instance_summaries (
@@ -552,6 +565,7 @@ fn analyze(ir: &Ir, collect_coverage: bool) -> (CombAnalysisResult, Vec<Analyzer
552565 ) ;
553566 extend_unique_errors ( & mut coverage, module_coverage) ;
554567 check_graph ( module, & graph, & mut loops) ;
568+ summaries. analyzed_templates . insert ( module. name ) ;
555569 if !reasons. is_empty ( ) {
556570 incomplete. push ( IncompleteCombAnalysis {
557571 module : module. name . to_string ( ) ,
@@ -648,6 +662,7 @@ fn ensure_instance_summaries(
648662 ) ;
649663 extend_unique_errors ( coverage, child_coverage) ;
650664 check_graph ( child, & graph, loops) ;
665+ summaries. analyzed_templates . insert ( child. name ) ;
651666 let summary = compute_module_summary ( child, & graph, & bit_part) ;
652667 summaries. modules . insert ( key. clone ( ) , summary) ;
653668 if !reasons. is_empty ( ) {
@@ -695,7 +710,24 @@ fn module_analysis_order(ir: &Ir) -> Vec<usize> {
695710 . iter ( )
696711 . map ( |component| matches ! ( component, Component :: Module ( _) ) )
697712 . collect :: < Vec < _ > > ( ) ;
698- order_from_dependencies ( & is_module, & deps, & rev_deps)
713+ let order = order_from_dependencies ( & is_module, & deps, & rev_deps) ;
714+ prioritize_module_roots ( order, & rev_deps)
715+ }
716+
717+ fn prioritize_module_roots (
718+ order : Vec < usize > ,
719+ reverse_dependencies : & [ HashSet < usize > ] ,
720+ ) -> Vec < usize > {
721+ // Start from modules which are not instantiated by another source
722+ // template. Their recursive summary walk checks concrete children before
723+ // those children's redundant standalone entries are encountered. Keep
724+ // the previous deterministic order within both groups; a name-cycle has
725+ // no root and remains in the fallback group.
726+ let ( mut roots, remaining) : ( Vec < _ > , Vec < _ > ) = order
727+ . into_iter ( )
728+ . partition ( |index| reverse_dependencies[ * index] . is_empty ( ) ) ;
729+ roots. extend ( remaining) ;
730+ roots
699731}
700732
701733fn order_from_dependencies (
@@ -3501,6 +3533,12 @@ mod memory_ssa_tests {
35013533
35023534 let order = order_from_dependencies ( & is_module, & deps, & rev_deps) ;
35033535 assert_eq ! ( order, vec![ 1 , 3 , 0 , 2 ] ) ;
3536+
3537+ // Why this case exists: concrete instance summaries are discovered
3538+ // only while visiting a parent. Roots must therefore run before the
3539+ // child-first fallback order, while a source-name cycle remains in a
3540+ // deterministic fallback position instead of disappearing.
3541+ assert_eq ! ( prioritize_module_roots( order, & rev_deps) , vec![ 3 , 2 , 1 , 0 ] ) ;
35043542 }
35053543
35063544 #[ test]
0 commit comments