@@ -192,18 +192,34 @@ function namedkeys(cl::CodeLinks)
192
192
end
193
193
194
194
function direct_links! (cl:: CodeLinks , src:: CodeInfo )
195
+ # Utility for when a stmt itself contains a CodeInfo
196
+ function add_inner! (cl:: CodeLinks , icl:: CodeLinks , idx)
197
+ for (name, _) in icl. nameassigns
198
+ assigns = get (cl. nameassigns, name, nothing )
199
+ if assigns === nothing
200
+ cl. nameassigns[name] = assigns = Int[]
201
+ end
202
+ push! (assigns, idx)
203
+ end
204
+ for (name, _) in icl. namesuccs
205
+ succs = get (cl. namesuccs, name, nothing )
206
+ if succs === nothing
207
+ cl. namesuccs[name] = succs = Links ()
208
+ end
209
+ push! (succs. ssas, idx)
210
+ end
211
+ end
212
+
195
213
for (i, stmt) in enumerate (src. code)
196
214
if isexpr (stmt, :thunk ) && isa (stmt. args[1 ], CodeInfo)
197
215
icl = CodeLinks (stmt. args[1 ])
198
- for (name, _) in icl. nameassigns
199
- assign = get (cl. nameassigns, name, nothing )
200
- if assign === nothing
201
- cl. nameassigns[name] = assign = Int[]
202
- end
203
- push! (assign, i)
204
- end
216
+ add_inner! (cl, icl, i)
205
217
continue
206
218
elseif isa (stmt, Expr) && stmt. head ∈ trackedheads
219
+ if stmt. head === :method && length (stmt. args) === 3 && isa (stmt. args[3 ], CodeInfo)
220
+ icl = CodeLinks (stmt. args[3 ])
221
+ add_inner! (cl, icl, i)
222
+ end
207
223
name = stmt. args[1 ]
208
224
if isa (name, Symbol)
209
225
assign = get (cl. nameassigns, name, nothing )
@@ -511,6 +527,27 @@ function postprint_lineedges(io::IO, idx::Int, edges::CodeEdges, bbchanged::Bool
511
527
return nothing
512
528
end
513
529
530
+ function terminal_preds (i:: Int , edges:: CodeEdges )
531
+ function terminal_preds! (s, j, edges, covered)
532
+ j ∈ covered && return s
533
+ push! (covered, j)
534
+ preds = edges. preds[j]
535
+ if isempty (preds)
536
+ push! (s, j)
537
+ else
538
+ for p in preds
539
+ terminal_preds! (s, p, edges, covered)
540
+ end
541
+ end
542
+ return s
543
+ end
544
+ s, covered = BitSet (), BitSet ()
545
+ push! (covered, i)
546
+ for p in edges. preds[i]
547
+ terminal_preds! (s, p, edges, covered)
548
+ end
549
+ return s
550
+ end
514
551
515
552
"""
516
553
isrequired = lines_required(obj::Union{Symbol,GlobalRef}, src::CodeInfo, edges::CodeEdges)
@@ -551,95 +588,153 @@ end
551
588
function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges )
552
589
# Do a traveral of "numbered" predecessors
553
590
function add_preds! (isrequired, idx, edges:: CodeEdges )
591
+ changed = false
554
592
preds = edges. preds[idx]
555
593
for p in preds
556
594
isrequired[p] && continue
557
595
isrequired[p] = true
596
+ changed = true
558
597
add_preds! (isrequired, p, edges)
559
598
end
560
- return isrequired
599
+ return changed
561
600
end
562
601
function add_succs! (isrequired, idx, edges:: CodeEdges , succs)
602
+ changed = false
563
603
for p in succs
564
604
isrequired[p] && continue
565
605
isrequired[p] = true
606
+ changed = true
566
607
add_succs! (isrequired, p, edges, edges. succs[p])
567
608
end
568
- return isrequired
609
+ return changed
610
+ end
611
+ function add_obj! (isrequired, objs, obj, edges:: CodeEdges )
612
+ changed = false
613
+ for d in edges. byname[obj]. assigned
614
+ isrequired[d] || add_preds! (isrequired, d, edges)
615
+ isrequired[d] = true
616
+ changed = true
617
+ end
618
+ push! (objs, obj)
619
+ return changed
569
620
end
570
621
622
+ objsnew = Set {Union{Symbol,GlobalRef}} ()
623
+ for obj in objs
624
+ add_obj! (isrequired, objsnew, obj, edges)
625
+ end
626
+ objs = objsnew
571
627
bbs = Core. Compiler. compute_basic_blocks (src. code) # needed for control-flow analysis
572
628
changed = true
573
629
iter = 0
574
630
while changed
575
631
changed = false
576
- # Add "named" object dependencies
577
- for obj in objs
578
- def = edges. byname[obj]. assigned
579
- if ! all (i-> isrequired[i], def)
580
- changed = true
581
- for d in def
582
- isrequired[d] = true
583
- add_preds! (isrequired, d, edges)
584
- if isexpr (src. code[d], :thunk ) && startswith (String (obj), ' #' )
585
- # For anonymous types, we also want their associated methods
586
- add_succs! (isrequired, d, edges, edges. byname[obj]. succs)
587
- end
588
- end
589
- end
590
- end
591
- # Add "numbered" dependencies
632
+ # Handle ssa predecessors
592
633
for idx = 1 : length (isrequired)
593
634
if isrequired[idx]
594
- preds = edges. preds[idx]
595
- if ! all (i-> isrequired[i], preds)
596
- changed = true
597
- isrequired[preds] .= true
598
- end
635
+ changed |= add_preds! (isrequired, idx, edges)
636
+ end
637
+ end
638
+ # Handle named dependencies
639
+ for (obj, uses) in edges. byname
640
+ obj ∈ objs && continue
641
+ if any (view (isrequired, uses. succs))
642
+ changed |= add_obj! (isrequired, objs, obj, edges)
599
643
end
600
644
end
601
645
# Add control-flow. For any basic block with an evaluated statement inside it,
602
646
# check to see if the block has any successors, and if so mark that block's exit statement.
603
647
# Likewise, any preceding blocks should have *their* exit statement marked.
604
- for (i , bb) in enumerate (bbs. blocks)
648
+ for (ibb , bb) in enumerate (bbs. blocks)
605
649
r = rng (bb)
606
650
if any (view (isrequired, r))
607
651
# if !isempty(bb.succs)
608
- if i != length (bbs. blocks)
652
+ if ibb != length (bbs. blocks)
609
653
idxlast = r[end ]
610
654
changed |= ! isrequired[idxlast]
611
655
isrequired[idxlast] = true
612
656
end
613
- for ibb in bb. preds
614
- rpred = rng (bbs. blocks[ibb ])
657
+ for ibbp in bb. preds
658
+ rpred = rng (bbs. blocks[ibbp ])
615
659
idxlast = rpred[end ]
616
660
changed |= ! isrequired[idxlast]
617
661
isrequired[idxlast] = true
618
662
end
619
- for ibb in bb. succs
620
- ibb == length (bbs. blocks) && continue
621
- rpred = rng (bbs. blocks[ibb ])
663
+ for ibbs in bb. succs
664
+ ibbs == length (bbs. blocks) && continue
665
+ rpred = rng (bbs. blocks[ibbs ])
622
666
idxlast = rpred[end ]
623
667
changed |= ! isrequired[idxlast]
624
668
isrequired[idxlast] = true
625
669
end
626
670
end
627
671
end
628
- # In preparation for the next round, add any new named objects
629
- # required by these dependencies
630
- for (obj, uses) in edges. byname
631
- obj ∈ objs && continue
632
- if any (i-> isrequired[i], uses. succs)
633
- push! (objs, obj)
634
- changed = true
672
+ # So far, everything is generic graph traversal. Now we add some domain-specific information.
673
+ # New struct definitions, including their constructors, get spread out over many
674
+ # statements. If we're evaluating any of them, it's important to evaluate *all* of them.
675
+ for (idx, stmt) in enumerate (src. code)
676
+ isrequired[idx] || continue
677
+ if isexpr (stmt, :(= ))
678
+ stmt = stmt. args[2 ]
679
+ end
680
+ # Is this a struct definition?
681
+ if (isa (stmt, Expr) && stmt. head ∈ structheads) || # < Julia 1.5
682
+ (isexpr (stmt, :call ) && callee_matches (stmt. args[1 ], Core, :_structtype )) # >= Julia 1.5
683
+ stmt = stmt:: Expr
684
+ name = stmt. args[stmt. head === :call ? 3 : 1 ]
685
+ if isa (name, QuoteNode)
686
+ name = name. value
687
+ end
688
+ name = name:: NamedVar
689
+ # Some lines we need have been marked as successors of this name
690
+ for d in edges. byname[name]. succs
691
+ stmt2 = src. code[d]
692
+ if isa (stmt2, Expr)
693
+ head = stmt2. head
694
+ if head === :method || head === :global || head === :const
695
+ changed |= ! isrequired[d]
696
+ isrequired[d] = true
697
+ end
698
+ end
699
+ # Julia 1.5+: others are successor of a slotnum->ssa load
700
+ if isslotnum (stmt2)
701
+ for s in edges. succs[d]
702
+ stmt3 = src. code[s]
703
+ if isexpr (stmt3, :call ) && (callee_matches (stmt3. args[1 ], Core, :_setsuper! ) ||
704
+ callee_matches (stmt3. args[1 ], Core, :_typebody! ))
705
+ changed |= ! isrequired[s]
706
+ isrequired[s] = true
707
+ end
708
+ end
709
+ end
710
+ # Julia 1.5+: for non-parametric types, the Core._setsuper! call happens without the slotname->ssa load
711
+ if isexpr (stmt2, :call ) && callee_matches ((stmt2:: Expr ). args[1 ], Core, :_setsuper! )
712
+ changed |= ! isrequired[d]
713
+ isrequired[d] = true
714
+ end
715
+ end
716
+ end
717
+ # Anonymous functions may not yet include the method definition
718
+ if isexpr (stmt, :thunk ) && isanonymous_typedef (stmt. args[1 ])
719
+ i = idx + 1
720
+ while i <= length (src. code) && ! ismethod3 (src. code[i])
721
+ i += 1
722
+ end
723
+ if i <= length (src. code) && src. code[i]. args[1 ] == false
724
+ tpreds = terminal_preds (i, edges)
725
+ if minimum (tpreds) == idx
726
+ changed |= ! isrequired[i]
727
+ isrequired[i] = true
728
+ end
729
+ end
635
730
end
636
731
end
637
732
iter += 1 # just for diagnostics
638
733
end
639
734
return isrequired
640
735
end
641
736
642
- function caller_matches (f, mod, sym)
737
+ function callee_matches (f, mod, sym)
643
738
is_global_ref (f, mod, sym) && return true
644
739
if isdefined (mod, sym)
645
740
is_quotenode (f, getfield (mod, sym)) && return true
0 commit comments