@@ -173,6 +173,41 @@ function postprint_linelinks(io::IO, idx::Int, src::CodeInfo, cl::CodeLinks, bbc
173
173
return nothing
174
174
end
175
175
176
+ struct CFGShortCut
177
+ from:: Int # pc of GotoIfNot with inactive 𝑰𝑵𝑭𝑳 blocks
178
+ to:: Int # pc of the entry of the nearest common post-dominator of the GotoIfNot's successors
179
+ end
180
+
181
+ """
182
+ controller::SelectiveEvalController
183
+
184
+ When this object is passed as the `recurse` argument of `selective_eval!`,
185
+ the selective execution is adjusted as follows:
186
+
187
+ - **Implicit return**: In Julia's IR representation (`CodeInfo`), the final block does not
188
+ necessarily return and may `goto` another block. And if the `return` statement is not
189
+ included in the slice in such cases, it is necessary to terminate `selective_eval!` when
190
+ execution reaches such implicit return statements. `controller.implicit_returns` records
191
+ the PCs of such return statements, and `selective_eval!` will return when reaching those statements.
192
+
193
+ - **CFG short-cut**: When the successors of a conditional branch are inactive, and it is
194
+ safe to move the program counter from the conditional branch to the nearest common
195
+ post-dominator of those successors, this short-cut is taken.
196
+ This short-cut is not merely an optimization but is actually essential for the correctness
197
+ of the selective execution. This is because, in `CodeInfo`, even if we simply fall-through
198
+ dead blocks (i.e., increment the program counter without executing the statements of those
199
+ blocks), it does not necessarily lead to the nearest common post-dominator block.
200
+
201
+ These adjustments are necessary for performing selective execution correctly.
202
+ [`lines_required`](@ref) or [`lines_required!`](@ref) will update the `SelectiveEvalController`
203
+ passed as an argument to be appropriate for the program slice generated.
204
+ """
205
+ struct SelectiveEvalController{RC}
206
+ inner:: RC # N.B. this doesn't support recursive selective evaluation
207
+ implicit_returns:: BitSet # pc where selective execution should terminate even if they're inactive
208
+ shortcuts:: Vector{CFGShortCut}
209
+ SelectiveEvalController (inner:: RC = finish_and_return!) where RC = new {RC} (inner, BitSet (), CFGShortCut[])
210
+ end
176
211
177
212
function namedkeys (cl:: CodeLinks )
178
213
ukeys = Set {GlobalRef} ()
566
601
567
602
568
603
"""
569
- isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges)
570
- isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges)
604
+ isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges, [controller::SelectiveEvalController] )
605
+ isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges, [controller::SelectiveEvalController] )
571
606
572
607
Determine which lines might need to be executed to evaluate `obj` or the statement indexed by `idx`.
573
608
If `isrequired[i]` is `false`, the `i`th statement is *not* required.
@@ -576,21 +611,26 @@ will end up skipping a subset of such statements, perhaps while repeating others
576
611
577
612
See also [`lines_required!`](@ref) and [`selective_eval!`](@ref).
578
613
"""
579
- function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
614
+ function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ,
615
+ controller:: SelectiveEvalController = SelectiveEvalController ();
616
+ kwargs... )
580
617
isrequired = falses (length (edges. preds))
581
618
objs = Set {GlobalRef} ([obj])
582
- return lines_required! (isrequired, objs, src, edges; kwargs... )
619
+ return lines_required! (isrequired, objs, src, edges, controller ; kwargs... )
583
620
end
584
621
585
- function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
622
+ function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ,
623
+ controller:: SelectiveEvalController = SelectiveEvalController ();
624
+ kwargs... )
586
625
isrequired = falses (length (edges. preds))
587
626
isrequired[idx] = true
588
627
objs = Set {GlobalRef} ()
589
- return lines_required! (isrequired, objs, src, edges; kwargs... )
628
+ return lines_required! (isrequired, objs, src, edges, controller ; kwargs... )
590
629
end
591
630
592
631
"""
593
- lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
632
+ lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges,
633
+ [controller::SelectiveEvalController];
594
634
norequire = ())
595
635
596
636
Like `lines_required`, but where `isrequired[idx]` has already been set to `true` for all statements
@@ -602,9 +642,11 @@ should _not_ be marked as a requirement.
602
642
For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges)` if you're
603
643
extracting method signatures and not evaluating new definitions.
604
644
"""
605
- function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
645
+ function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ,
646
+ controller:: SelectiveEvalController = SelectiveEvalController ();
647
+ kwargs... )
606
648
objs = Set {GlobalRef} ()
607
- return lines_required! (isrequired, objs, src, edges; kwargs... )
649
+ return lines_required! (isrequired, objs, src, edges, controller ; kwargs... )
608
650
end
609
651
610
652
function exclude_named_typedefs (src:: CodeInfo , edges:: CodeEdges )
@@ -624,7 +666,9 @@ function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
624
666
return norequire
625
667
end
626
668
627
- function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ; norequire = ())
669
+ function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ,
670
+ controller:: SelectiveEvalController = SelectiveEvalController ();
671
+ norequire = ())
628
672
# Mark any requested objects (their lines of assignment)
629
673
objs = add_requests! (isrequired, objs, edges, norequire)
630
674
@@ -659,7 +703,10 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
659
703
end
660
704
661
705
# now mark the active goto nodes
662
- add_active_gotos! (isrequired, src, cfg, postdomtree)
706
+ add_active_gotos! (isrequired, src, cfg, postdomtree, controller)
707
+
708
+ # check if there are any implicit return blocks
709
+ record_implcit_return! (controller, isrequired, cfg)
663
710
664
711
return isrequired
665
712
end
@@ -738,13 +785,14 @@ using Core.Compiler: CFG, BasicBlock, compute_basic_blocks
738
785
# The basic algorithm is based on what was proposed in [^Wei84]. If there is even one active
739
786
# block in the blocks reachable from a conditional branch up to its successors' nearest
740
787
# common post-dominator (referred to as 𝑰𝑵𝑭𝑳 in the paper), it is necessary to follow
741
- # that conditional branch and execute the code. Otherwise, execution can be short-circuited
788
+ # that conditional branch and execute the code. Otherwise, execution can be short-cut
742
789
# from the conditional branch to the nearest common post-dominator.
743
790
#
744
- # COMBAK: It is important to note that in Julia's intermediate code representation (`CodeInfo`),
745
- # "short-circuiting" a specific code region is not a simple task. Simply ignoring the path
746
- # to the post-dominator does not guarantee fall-through to the post-dominator. Therefore,
747
- # a more careful implementation is required for this aspect.
791
+ # It is important to note that in Julia's intermediate code representation (`CodeInfo`),
792
+ # "short-cutting" a specific code region is not a simple task. Simply incrementing the
793
+ # program counter without executing the statements of 𝑰𝑵𝑭𝑳 blocks does not guarantee that
794
+ # the program counter fall-throughs to the post-dominator.
795
+ # To handle such cases, `selective_eval!` needs to use `SelectiveEvalController`.
748
796
#
749
797
# [Wei84]: M. Weiser, "Program Slicing," IEEE Transactions on Software Engineering, 10, pages 352-357, July 1984.
750
798
function add_control_flow! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
@@ -819,8 +867,8 @@ function reachable_blocks(cfg, from_bb::Int, to_bb::Int)
819
867
return visited
820
868
end
821
869
822
- function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
823
- dead_blocks = compute_dead_blocks (isrequired, src, cfg, postdomtree)
870
+ function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, controller :: SelectiveEvalController )
871
+ dead_blocks = compute_dead_blocks! (isrequired, src, cfg, postdomtree, controller )
824
872
changed = false
825
873
for bbidx = 1 : length (cfg. blocks)
826
874
if bbidx ∉ dead_blocks
@@ -838,7 +886,7 @@ function add_active_gotos!(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
838
886
end
839
887
840
888
# find dead blocks using the same approach as `add_control_flow!`, for the converged `isrequired`
841
- function compute_dead_blocks (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
889
+ function compute_dead_blocks! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, controller :: SelectiveEvalController )
842
890
dead_blocks = BitSet ()
843
891
for bbidx = 1 : length (cfg. blocks)
844
892
bb = cfg. blocks[bbidx]
@@ -859,13 +907,31 @@ function compute_dead_blocks(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
859
907
end
860
908
if ! is_𝑰𝑵𝑭𝑳_active
861
909
union! (dead_blocks, delete! (𝑰𝑵𝑭𝑳, postdominator))
910
+ if postdominator ≠ 0
911
+ postdominator_bb = cfg. blocks[postdominator]
912
+ postdominator_entryidx = postdominator_bb. stmts[begin ]
913
+ push! (controller. shortcuts, CFGShortCut (termidx, postdominator_entryidx))
914
+ end
862
915
end
863
916
end
864
917
end
865
918
end
866
919
return dead_blocks
867
920
end
868
921
922
+ function record_implcit_return! (controller:: SelectiveEvalController , isrequired, cfg:: CFG )
923
+ for bbidx = 1 : length (cfg. blocks)
924
+ bb = cfg. blocks[bbidx]
925
+ if isempty (bb. succs)
926
+ i = findfirst (idx:: Int -> ! isrequired[idx], bb. stmts)
927
+ if ! isnothing (i)
928
+ push! (controller. implicit_returns, bb. stmts[i])
929
+ end
930
+ end
931
+ end
932
+ nothing
933
+ end
934
+
869
935
# Do a traveral of "numbered" predecessors and find statement ranges and names of type definitions
870
936
function find_typedefs (src:: CodeInfo )
871
937
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
@@ -988,6 +1054,42 @@ function add_inplace!(isrequired, src, edges, norequire)
988
1054
return changed
989
1055
end
990
1056
1057
+ function JuliaInterpreter. step_expr! (controller:: SelectiveEvalController , frame:: Frame , @nospecialize (node), istoplevel:: Bool )
1058
+ if frame. pc in controller. implicit_returns
1059
+ return nothing
1060
+ elseif node isa GotoIfNot
1061
+ for shortcut in controller. shortcuts
1062
+ if shortcut. from == frame. pc
1063
+ return frame. pc = shortcut. to
1064
+ end
1065
+ end
1066
+ end
1067
+ # TODO allow recursion: @invoke JuliaInterpreter.step_expr!(controller::Any, frame::Frame, node::Any, istoplevel::Bool)
1068
+ JuliaInterpreter. step_expr! (controller. inner, frame, node, istoplevel)
1069
+ end
1070
+
1071
+ next_or_nothing! (frame:: Frame ) = next_or_nothing! (finish_and_return!, frame)
1072
+ function next_or_nothing! (@nospecialize (recurse), frame:: Frame )
1073
+ pc = frame. pc
1074
+ if pc < nstatements (frame. framecode)
1075
+ return frame. pc = pc + 1
1076
+ end
1077
+ return nothing
1078
+ end
1079
+ function next_or_nothing! (controller:: SelectiveEvalController , frame:: Frame )
1080
+ if frame. pc in controller. implicit_returns
1081
+ return nothing
1082
+ elseif pc_expr (frame) isa GotoIfNot
1083
+ for shortcut in controller. shortcuts
1084
+ if shortcut. from == frame. pc
1085
+ return frame. pc = shortcut. to
1086
+ end
1087
+ end
1088
+ end
1089
+ # TODO allow recursion: @invoke next_or_nothing!(controller::Any, frame::Frame)
1090
+ next_or_nothing! (controller. inner, frame)
1091
+ end
1092
+
991
1093
"""
992
1094
selective_eval!([recurse], frame::Frame, isrequired::AbstractVector{Bool}, istoplevel=false)
993
1095
@@ -1000,6 +1102,15 @@ See [`selective_eval_fromstart!`](@ref) to have that performed automatically.
1000
1102
The default value for `recurse` is `JuliaInterpreter.finish_and_return!`.
1001
1103
`isrequired` pertains only to `frame` itself, not any of its callees.
1002
1104
1105
+ When `recurse::SelectiveEvalController` is specified, the selective evaluation execution
1106
+ becomes fully correct. Conversely, with the default `finish_and_return!`, selective
1107
+ evaluation may not be necessarily correct for all possible Julia code (see
1108
+ https://github.com/JuliaDebug/LoweredCodeUtils.jl/pull/99 for more details).
1109
+
1110
+ Ensure that the specified `controller` is properly synchronized with `isrequired`.
1111
+ Additionally note that, at present, it is not possible to recurse the `controller`.
1112
+ In other words, there is no system in place for interprocedural selective evaluation.
1113
+
1003
1114
This will return either a `BreakpointRef`, the value obtained from the last executed statement
1004
1115
(if stored to `frame.framedata.ssavlues`), or `nothing`.
1005
1116
Typically, assignment to a variable binding does not result in an ssa store by JuliaInterpreter.
0 commit comments