@@ -182,6 +182,66 @@ 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 SelectiveInterpreter{RC} <: Interpreter
215
+ # inner::RC # N.B. this doesn't support recursive selective evaluation
216
+ # implicit_returns::BitSet # pc where selective execution should terminate even if they're inactive
217
+ # shortcuts::Vector{CFGShortCut}
218
+ # SelectiveInterpreter(inner::RC=finish_and_return!) where RC = new{RC}(inner, BitSet(), CFGShortCut[])
219
+ # end
220
+
221
+ struct SelectiveEvalInfo
222
+ implicit_returns:: BitSet # pc where selective execution should terminate even if they're inactive
223
+ shortcuts:: Vector{CFGShortCut}
224
+ end
225
+ SelectiveEvalInfo () = SelectiveEvalInfo (BitSet (), CFGShortCut[])
226
+
227
+ """
228
+ struct SelectiveInterpreter{S<:Interpreter,T<:AbstractVector{Bool}} <: Interpreter
229
+ inner::S
230
+ isrequired::T
231
+ end
232
+
233
+ An `JuliaInterpreter.Interpreter` that executes only the statements marked `true` in `isrequired`.
234
+ Note that this inforeter does not recurse into callee frames.
235
+ That is, when `JuliaInterpreter.finish!(info::SelectiveEvalInfo, frame, ...)` is
236
+ performed, the `frame` will be executed selectively according to `info.isrequired`, but
237
+ any callee frames within it will be executed by `info.inner::Interpreter`, not by `info`.
238
+ """
239
+ struct SelectiveInterpreter{S<: Interpreter ,T<: AbstractVector{Bool} } <: Interpreter
240
+ inner:: S
241
+ isrequired:: T
242
+ info:: SelectiveEvalInfo
243
+ end
244
+
185
245
function namedkeys (cl:: CodeLinks )
186
246
ukeys = Set {GlobalRef} ()
187
247
for c in (cl. namepreds, cl. namesuccs, cl. nameassigns)
@@ -591,8 +651,8 @@ function terminal_preds!(s, j, edges, covered) # can't be an inner function bec
591
651
end
592
652
593
653
"""
594
- isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges)
595
- isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges)
654
+ isrequired = lines_required(obj::GlobalRef, src::CodeInfo, edges::CodeEdges, [info::SelectiveEvalInfo] )
655
+ isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges, [info::SelectiveEvalInfo] )
596
656
597
657
Determine which lines might need to be executed to evaluate `obj` or the statement indexed by `idx`.
598
658
If `isrequired[i]` is `false`, the `i`th statement is *not* required.
@@ -601,21 +661,26 @@ will end up skipping a subset of such statements, perhaps while repeating others
601
661
602
662
See also [`lines_required!`](@ref) and [`selective_eval!`](@ref).
603
663
"""
604
- function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
664
+ function lines_required (obj:: GlobalRef , src:: CodeInfo , edges:: CodeEdges ,
665
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
666
+ kwargs... )
605
667
isrequired = falses (length (edges. preds))
606
668
objs = Set {GlobalRef} ([obj])
607
- return lines_required! (isrequired, objs, src, edges; kwargs... )
669
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
608
670
end
609
671
610
- function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
672
+ function lines_required (idx:: Int , src:: CodeInfo , edges:: CodeEdges ,
673
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
674
+ kwargs... )
611
675
isrequired = falses (length (edges. preds))
612
676
isrequired[idx] = true
613
677
objs = Set {GlobalRef} ()
614
- return lines_required! (isrequired, objs, src, edges; kwargs... )
678
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
615
679
end
616
680
617
681
"""
618
- lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
682
+ lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges,
683
+ [info::SelectiveEvalInfo];
619
684
norequire = ())
620
685
621
686
Like `lines_required`, but where `isrequired[idx]` has already been set to `true` for all statements
@@ -627,9 +692,11 @@ should _not_ be marked as a requirement.
627
692
For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges)` if you're
628
693
extracting method signatures and not evaluating new definitions.
629
694
"""
630
- function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ; kwargs... )
695
+ function lines_required! (isrequired:: AbstractVector{Bool} , src:: CodeInfo , edges:: CodeEdges ,
696
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
697
+ kwargs... )
631
698
objs = Set {GlobalRef} ()
632
- return lines_required! (isrequired, objs, src, edges; kwargs... )
699
+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
633
700
end
634
701
635
702
function exclude_named_typedefs (src:: CodeInfo , edges:: CodeEdges )
@@ -649,7 +716,9 @@ function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
649
716
return norequire
650
717
end
651
718
652
- function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ; norequire = ())
719
+ function lines_required! (isrequired:: AbstractVector{Bool} , objs, src:: CodeInfo , edges:: CodeEdges ,
720
+ info:: SelectiveEvalInfo = SelectiveEvalInfo ();
721
+ norequire = ())
653
722
# Mark any requested objects (their lines of assignment)
654
723
objs = add_requests! (isrequired, objs, edges, norequire)
655
724
@@ -684,7 +753,10 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
684
753
end
685
754
686
755
# now mark the active goto nodes
687
- add_active_gotos! (isrequired, src, cfg, postdomtree)
756
+ add_active_gotos! (isrequired, src, cfg, postdomtree, info)
757
+
758
+ # check if there are any implicit return blocks
759
+ record_implcit_return! (info, isrequired, cfg)
688
760
689
761
return isrequired
690
762
end
@@ -768,13 +840,14 @@ end
768
840
# The basic algorithm is based on what was proposed in [^Wei84]. If there is even one active
769
841
# block in the blocks reachable from a conditional branch up to its successors' nearest
770
842
# 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
843
+ # that conditional branch and execute the code. Otherwise, execution can be short-cut
772
844
# from the conditional branch to the nearest common post-dominator.
773
845
#
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.
846
+ # It is important to note that in Julia's intermediate code representation (`CodeInfo`),
847
+ # "short-cutting" a specific code region is not a simple task. Simply incrementing the
848
+ # program counter without executing the statements of 𝑰𝑵𝑭𝑳 blocks does not guarantee that
849
+ # the program counter fall-throughs to the post-dominator.
850
+ # To handle such cases, `selective_eval!` needs to use `SelectiveInterpreter`.
778
851
#
779
852
# [Wei84]: M. Weiser, "Program Slicing," IEEE Transactions on Software Engineering, 10, pages 352-357, July 1984.
780
853
function add_control_flow! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
@@ -849,8 +922,8 @@ function reachable_blocks(cfg, from_bb::Int, to_bb::Int)
849
922
return visited
850
923
end
851
924
852
- function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
853
- dead_blocks = compute_dead_blocks (isrequired, src, cfg, postdomtree)
925
+ function add_active_gotos! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, info :: SelectiveEvalInfo )
926
+ dead_blocks = compute_dead_blocks! (isrequired, src, cfg, postdomtree, info )
854
927
changed = false
855
928
for bbidx = 1 : length (cfg. blocks)
856
929
if bbidx ∉ dead_blocks
@@ -868,7 +941,7 @@ function add_active_gotos!(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
868
941
end
869
942
870
943
# 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)
944
+ function compute_dead_blocks! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree, info :: SelectiveEvalInfo )
872
945
dead_blocks = BitSet ()
873
946
for bbidx = 1 : length (cfg. blocks)
874
947
bb = cfg. blocks[bbidx]
@@ -889,13 +962,31 @@ function compute_dead_blocks(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
889
962
end
890
963
if ! is_𝑰𝑵𝑭𝑳_active
891
964
union! (dead_blocks, delete! (𝑰𝑵𝑭𝑳, postdominator))
965
+ if postdominator ≠ 0
966
+ postdominator_bb = cfg. blocks[postdominator]
967
+ postdominator_entryidx = postdominator_bb. stmts[begin ]
968
+ push! (info. shortcuts, CFGShortCut (termidx, postdominator_entryidx))
969
+ end
892
970
end
893
971
end
894
972
end
895
973
end
896
974
return dead_blocks
897
975
end
898
976
977
+ function record_implcit_return! (info:: SelectiveEvalInfo , isrequired, cfg:: CFG )
978
+ for bbidx = 1 : length (cfg. blocks)
979
+ bb = cfg. blocks[bbidx]
980
+ if isempty (bb. succs)
981
+ i = findfirst (idx:: Int -> ! isrequired[idx], bb. stmts)
982
+ if ! isnothing (i)
983
+ push! (info. implicit_returns, bb. stmts[i])
984
+ end
985
+ end
986
+ end
987
+ nothing
988
+ end
989
+
899
990
# Do a traveral of "numbered" predecessors and find statement ranges and names of type definitions
900
991
function find_typedefs (src:: CodeInfo )
901
992
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
@@ -1018,26 +1109,19 @@ function add_inplace!(isrequired, src, edges, norequire)
1018
1109
return changed
1019
1110
end
1020
1111
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
1112
function JuliaInterpreter. step_expr! (interp:: SelectiveInterpreter , frame:: Frame , istoplevel:: Bool )
1038
1113
pc = frame. pc
1114
+ if pc in info. implicit_returns
1115
+ return nothing
1116
+ elseif pc_expr (frame) isa GotoIfNot
1117
+ for shortcut in info. shortcuts
1118
+ if shortcut. from == pc
1119
+ return frame. pc = shortcut. to
1120
+ end
1121
+ end
1122
+ end
1039
1123
if interp. isrequired[pc]
1040
- step_expr! (interp. inner, frame:: Frame , istoplevel:: Bool )
1124
+ step_expr! (interp. inner, frame, istoplevel)
1041
1125
else
1042
1126
next_or_nothing! (interp, frame)
1043
1127
end
@@ -1068,12 +1152,23 @@ See [`selective_eval_fromstart!`](@ref) to have that performed automatically.
1068
1152
1069
1153
`isrequired` pertains only to `frame` itself, not any of its callees.
1070
1154
1155
+ When `interp.info::SelectiveEvalInfo` is configured, the selective evaluation execution
1156
+ becomes fully correct. Conversely, with the default `finish_and_return!`, selective
1157
+ evaluation may not be necessarily correct for all possible Julia code (see
1158
+ https://github.com/JuliaDebug/LoweredCodeUtils.jl/pull/99 for more details).
1159
+
1160
+ Ensure that the specified `interp` is properly synchronized with `isrequired`.
1161
+ Additionally note that, at present, it is not possible to recurse the `interp`.
1162
+ In other words, there is no system in place for inforocedural selective evaluation.
1163
+
1071
1164
This will return either a `BreakpointRef`, the value obtained from the last executed statement
1072
1165
(if stored to `frame.framedata.ssavlues`), or `nothing`.
1073
1166
Typically, assignment to a variable binding does not result in an ssa store by JuliaInterpreter.
1074
1167
"""
1168
+ selective_eval! (interp:: SelectiveInterpreter , frame:: Frame , istoplevel:: Bool = false ) =
1169
+ JuliaInterpreter. finish_and_return! (interp, frame, istoplevel)
1075
1170
selective_eval! (interp:: Interpreter , frame:: Frame , isrequired:: AbstractVector{Bool} , istoplevel:: Bool = false ) =
1076
- JuliaInterpreter . finish_and_return ! (SelectiveInterpreter (interp, isrequired), frame, istoplevel)
1171
+ selective_eval ! (SelectiveInterpreter (interp, isrequired, SelectiveEvalInfo () ), frame, istoplevel)
1077
1172
selective_eval! (args... ) = selective_eval! (RecursiveInterpreter (), args... )
1078
1173
1079
1174
"""
0 commit comments