@@ -182,6 +182,59 @@ function postprint_linelinks(io::IO, idx::Int, src::CodeInfo, cl::CodeLinks, bbc
182
182
return nothing
183
183
end
184
184
185
+ struct CFGShortCut
186
+ from:: Int # pc of GotoIfNot with inactive 𝑰𝑵𝑭𝑳 blocks
187
+ to:: Int # pc of the entry of the nearest common post-dominator of the GotoIfNot's successors
188
+ end
189
+
190
+ """
191
+ info::SelectiveEvalInfo
192
+
193
+ When this object is passed as the `recurse` argument of `selective_eval!`,
194
+ the selective execution is adjusted as follows:
195
+
196
+ - **Implicit return**: In Julia's IR representation (`CodeInfo`), the final block does not
197
+ necessarily return and may `goto` another block. And if the `return` statement is not
198
+ included in the slice in such cases, it is necessary to terminate `selective_eval!` when
199
+ execution reaches such implicit return statements. `info.implicit_returns` records
200
+ the PCs of such return statements, and `selective_eval!` will return when reaching those statements.
201
+
202
+ - **CFG short-cut**: When the successors of a conditional branch are inactive, and it is
203
+ safe to move the program counter from the conditional branch to the nearest common
204
+ post-dominator of those successors, this short-cut is taken.
205
+ This short-cut is not merely an optimization but is actually essential for the correctness
206
+ of the selective execution. This is because, in `CodeInfo`, even if we simply fall-through
207
+ dead blocks (i.e., increment the program counter without executing the statements of those
208
+ blocks), it does not necessarily lead to the nearest common post-dominator block.
209
+
210
+ These adjustments are necessary for performing selective execution correctly.
211
+ [`lines_required`](@ref) or [`lines_required!`](@ref) will update the `SelectiveInterpreter`
212
+ passed as an argument to be appropriate for the program slice generated.
213
+ """
214
+ struct SelectiveEvalInfo
215
+ implicit_returns:: BitSet # pc where selective execution should terminate even if they're inactive
216
+ shortcuts:: Vector{CFGShortCut}
217
+ end
218
+ SelectiveEvalInfo () = SelectiveEvalInfo (BitSet (), CFGShortCut[])
219
+
220
+ """
221
+ struct SelectiveInterpreter{S<:Interpreter,T<:AbstractVector{Bool}} <: Interpreter
222
+ inner::S
223
+ isrequired::T
224
+ end
225
+
226
+ An `JuliaInterpreter.Interpreter` that executes only the statements marked `true` in `isrequired`.
227
+ Note that this inforeter does not recurse into callee frames.
228
+ That is, when `JuliaInterpreter.finish!(info::SelectiveEvalInfo, frame, ...)` is
229
+ performed, the `frame` will be executed selectively according to `info.isrequired`, but
230
+ any callee frames within it will be executed by `info.inner::Interpreter`, not by `info`.
231
+ """
232
+ struct SelectiveInterpreter{S<: Interpreter ,T<: AbstractVector{Bool} } <: Interpreter
233
+ inner:: S
234
+ isrequired:: T
235
+ info:: SelectiveEvalInfo
236
+ end
237
+
185
238
function namedkeys (cl:: CodeLinks )
186
239
ukeys = Set {GlobalRef} ()
187
240
for c in (cl. namepreds, cl. namesuccs, cl. nameassigns)
@@ -591,8 +644,8 @@ function terminal_preds!(s, j, edges, covered) # can't be an inner function bec
591
644
end
592
645
593
646
"""
594
- isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges)
595
- isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges)
647
+ isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges, [info::SelectiveEvalInfo] )
648
+ isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges, [info::SelectiveEvalInfo] )
596
649
597
650
Determine which lines might need to be executed to evaluate `obj` or the statement indexed by `idx`.
598
651
If `isrequired[i]` is `false`, the `i`th statement is *not* required.
@@ -601,21 +654,26 @@ will end up skipping a subset of such statements, perhaps while repeating others
601
654
602
655
See also [`lines_required!`](@ref) and [`selective_eval!`](@ref).
603
656
"""
604
- function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
657
+ function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ,
658
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
659
+ kwargs... )
605
660
isrequired = falses (length (edges. preds))
606
661
objs = Set {GlobalRef} ([obj])
607
- return lines_required! (isrequired, objs, src, edges; kwargs... )
662
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
608
663
end
609
664
610
- function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
665
+ function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ,
666
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
667
+ kwargs... )
611
668
isrequired = falses (length (edges. preds))
612
669
isrequired[idx] = true
613
670
objs = Set {GlobalRef} ()
614
- return lines_required! (isrequired, objs, src, edges; kwargs... )
671
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
615
672
end
616
673
617
674
"""
618
- lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
675
+ lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges,
676
+ [info::SelectiveEvalInfo];
619
677
norequire = ())
620
678
621
679
Like `lines_required`, but where `isrequired[idx]` has already been set to `true` for all statements
@@ -627,9 +685,11 @@ should _not_ be marked as a requirement.
627
685
For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges)` if you're
628
686
extracting method signatures and not evaluating new definitions.
629
687
"""
630
- function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
688
+ function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ,
689
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
690
+ kwargs... )
631
691
objs = Set {GlobalRef} ()
632
- return lines_required! (isrequired, objs, src, edges; kwargs... )
692
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
633
693
end
634
694
635
695
function exclude_named_typedefs (src:: CodeInfo , edges:: CodeEdges )
@@ -649,7 +709,9 @@ function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
649
709
return norequire
650
710
end
651
711
652
- function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ; norequire = ())
712
+ function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ,
713
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
714
+ norequire = ())
653
715
# Mark any requested objects (their lines of assignment)
654
716
objs = add_requests! (isrequired, objs, edges, norequire)
655
717
@@ -684,7 +746,10 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
684
746
end
685
747
686
748
# now mark the active goto nodes
687
- add_active_gotos! (isrequired, src, cfg, postdomtree)
749
+ add_active_gotos! (isrequired, src, cfg, postdomtree, info)
750
+
751
+ # check if there are any implicit return blocks
752
+ record_implcit_return! (info, isrequired, cfg)
688
753
689
754
return isrequired
690
755
end
@@ -762,19 +827,19 @@ end
762
827
763
828
# # Add control-flow
764
829
765
-
766
830
# The goal of this function is to request concretization of the minimal necessary control
767
831
# flow to evaluate statements whose concretization have already been requested.
768
832
# The basic algorithm is based on what was proposed in [^Wei84]. If there is even one active
769
833
# block in the blocks reachable from a conditional branch up to its successors' nearest
770
834
# common post-dominator (referred to as 𝑰𝑵𝑭𝑳 in the paper), it is necessary to follow
771
- # that conditional branch and execute the code. Otherwise, execution can be short-circuited
835
+ # that conditional branch and execute the code. Otherwise, execution can be short-cut
772
836
# from the conditional branch to the nearest common post-dominator.
773
837
#
774
- # COMBAK: It is important to note that in Julia's intermediate code representation (`CodeInfo`),
775
- # "short-circuiting" a specific code region is not a simple task. Simply ignoring the path
776
- # to the post-dominator does not guarantee fall-through to the post-dominator. Therefore,
777
- # a more careful implementation is required for this aspect.
838
+ # It is important to note that in Julia's intermediate code representation (`CodeInfo`),
839
+ # "short-cutting" a specific code region is not a simple task. Simply incrementing the
840
+ # program counter without executing the statements of 𝑰𝑵𝑭𝑳 blocks does not guarantee that
841
+ # the program counter fall-throughs to the post-dominator.
842
+ # To handle such cases, `selective_eval!` needs to use `SelectiveInterpreter`.
778
843
#
779
844
# [Wei84]: M. Weiser, "Program Slicing," IEEE Transactions on Software Engineering, 10, pages 352-357, July 1984.
780
845
function add_control_flow! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
@@ -849,8 +914,8 @@ function reachable_blocks(cfg, from_bb::Int, to_bb::Int)
849
914
return visited
850
915
end
851
916
852
- function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
853
- dead_blocks = compute_dead_blocks (isrequired, src, cfg, postdomtree)
917
+ function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, info :: SelectiveEvalInfo )
918
+ dead_blocks = compute_dead_blocks! (isrequired, src, cfg, postdomtree, info )
854
919
changed = false
855
920
for bbidx = 1 : length (cfg. blocks)
856
921
if bbidx ∉ dead_blocks
@@ -868,7 +933,7 @@ function add_active_gotos!(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
868
933
end
869
934
870
935
# find dead blocks using the same approach as `add_control_flow!`, for the converged `isrequired`
871
- function compute_dead_blocks (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
936
+ function compute_dead_blocks! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, info :: SelectiveEvalInfo )
872
937
dead_blocks = BitSet ()
873
938
for bbidx = 1 : length (cfg. blocks)
874
939
bb = cfg. blocks[bbidx]
@@ -889,13 +954,31 @@ function compute_dead_blocks(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
889
954
end
890
955
if ! is_𝑰𝑵𝑭𝑳_active
891
956
union! (dead_blocks, delete! (𝑰𝑵𝑭𝑳, postdominator))
957
+ if postdominator ≠ 0
958
+ postdominator_bb = cfg. blocks[postdominator]
959
+ postdominator_entryidx = postdominator_bb. stmts[begin ]
960
+ push! (info. shortcuts, CFGShortCut (termidx, postdominator_entryidx))
961
+ end
892
962
end
893
963
end
894
964
end
895
965
end
896
966
return dead_blocks
897
967
end
898
968
969
+ function record_implcit_return! (info:: SelectiveEvalInfo , isrequired, cfg:: CFG )
970
+ for bbidx = 1 : length (cfg. blocks)
971
+ bb = cfg. blocks[bbidx]
972
+ if isempty (bb. succs)
973
+ i = findfirst (idx:: Int -> ! isrequired[idx], bb. stmts)
974
+ if ! isnothing (i)
975
+ push! (info. implicit_returns, bb. stmts[i])
976
+ end
977
+ end
978
+ end
979
+ nothing
980
+ end
981
+
899
982
# Do a traveral of "numbered" predecessors and find statement ranges and names of type definitions
900
983
function find_typedefs (src:: CodeInfo )
901
984
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
@@ -1018,26 +1101,19 @@ function add_inplace!(isrequired, src, edges, norequire)
1018
1101
return changed
1019
1102
end
1020
1103
1021
- """
1022
- struct SelectiveInterpreter{S<:Interpreter,T<:AbstractVector{Bool}} <: Interpreter
1023
- inner::S
1024
- isrequired::T
1025
- end
1026
-
1027
- An `JuliaInterpreter.Interpreter` that executes only the statements marked `true` in `isrequired`.
1028
- Note that this interpreter does not recurse into callee frames.
1029
- That is, when `JuliaInterpreter.finish!(interp::SelectiveInterpreter, frame, ...)` is
1030
- performed, the `frame` will be executed selectively according to `interp.isrequired`, but
1031
- any callee frames within it will be executed by `interp.inner::Interpreter`, not by `interp`.
1032
- """
1033
- struct SelectiveInterpreter{S<: Interpreter ,T<: AbstractVector{Bool} } <: Interpreter
1034
- inner:: S
1035
- isrequired:: T
1036
- end
1037
1104
function JuliaInterpreter. step_expr! (interp:: SelectiveInterpreter , frame:: Frame , istoplevel:: Bool )
1038
1105
pc = frame. pc
1106
+ if pc in interp. info. implicit_returns
1107
+ return nothing
1108
+ elseif pc_expr (frame) isa GotoIfNot
1109
+ for shortcut in interp. info. shortcuts
1110
+ if shortcut. from == pc
1111
+ return frame. pc = shortcut. to
1112
+ end
1113
+ end
1114
+ end
1039
1115
if interp. isrequired[pc]
1040
- step_expr! (interp. inner, frame:: Frame , istoplevel:: Bool )
1116
+ step_expr! (interp. inner, frame, istoplevel)
1041
1117
else
1042
1118
next_or_nothing! (interp, frame)
1043
1119
end
@@ -1068,12 +1144,23 @@ See [`selective_eval_fromstart!`](@ref) to have that performed automatically.
1068
1144
1069
1145
`isrequired` pertains only to `frame` itself, not any of its callees.
1070
1146
1147
+ When `interp.info::SelectiveEvalInfo` is configured, the selective evaluation execution
1148
+ becomes fully correct. Conversely, with the default `finish_and_return!`, selective
1149
+ evaluation may not be necessarily correct for all possible Julia code (see
1150
+ https://github.com/JuliaDebug/LoweredCodeUtils.jl/pull/99 for more details).
1151
+
1152
+ Ensure that the specified `interp` is properly synchronized with `isrequired`.
1153
+ Additionally note that, at present, it is not possible to recurse the `interp`.
1154
+ In other words, there is no system in place for inforocedural selective evaluation.
1155
+
1071
1156
This will return either a `BreakpointRef`, the value obtained from the last executed statement
1072
1157
(if stored to `frame.framedata.ssavlues`), or `nothing`.
1073
1158
Typically, assignment to a variable binding does not result in an ssa store by JuliaInterpreter.
1074
1159
"""
1075
- selective_eval! (interp:: Interpreter , frame:: Frame , isrequired:: AbstractVector{Bool} , istoplevel:: Bool = false ) =
1076
- JuliaInterpreter. finish_and_return! (SelectiveInterpreter (interp, isrequired), frame, istoplevel)
1160
+ function selective_eval! (interp:: Interpreter , frame:: Frame , isrequired:: AbstractVector{Bool} , istoplevel:: Bool = false )
1161
+ interp = SelectiveInterpreter (interp, isrequired, SelectiveEvalInfo ())
1162
+ JuliaInterpreter. finish_and_return! (interp, frame, istoplevel)
1163
+ end
1077
1164
selective_eval! (args... ) = selective_eval! (RecursiveInterpreter (), args... )
1078
1165
1079
1166
"""
0 commit comments