@@ -182,6 +182,66 @@ function postprint_linelinks(io::IO, idx::Int, src::CodeInfo, cl::CodeLinks, bbc
182182 return nothing
183183end
184184
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+
185245function namedkeys (cl:: CodeLinks )
186246 ukeys = Set {GlobalRef} ()
187247 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
591651end
592652
593653"""
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] )
596656
597657Determine which lines might need to be executed to evaluate `obj` or the statement indexed by `idx`.
598658If `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
601661
602662See also [`lines_required!`](@ref) and [`selective_eval!`](@ref).
603663"""
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... )
605667 isrequired = falses (length (edges. preds))
606668 objs = Set {GlobalRef} ([obj])
607- return lines_required! (isrequired, objs, src, edges; kwargs... )
669+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
608670end
609671
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... )
611675 isrequired = falses (length (edges. preds))
612676 isrequired[idx] = true
613677 objs = Set {GlobalRef} ()
614- return lines_required! (isrequired, objs, src, edges; kwargs... )
678+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
615679end
616680
617681"""
618- lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
682+ lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges,
683+ [info::SelectiveEvalInfo];
619684 norequire = ())
620685
621686Like `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.
627692For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges)` if you're
628693extracting method signatures and not evaluating new definitions.
629694"""
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... )
631698 objs = Set {GlobalRef} ()
632- return lines_required! (isrequired, objs, src, edges; kwargs... )
699+ return lines_required! (isrequired, objs, src, edges, info ; kwargs... )
633700end
634701
635702function exclude_named_typedefs (src:: CodeInfo , edges:: CodeEdges )
@@ -649,7 +716,9 @@ function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
649716 return norequire
650717end
651718
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 = ())
653722 # Mark any requested objects (their lines of assignment)
654723 objs = add_requests! (isrequired, objs, edges, norequire)
655724
@@ -684,7 +753,10 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
684753 end
685754
686755 # 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)
688760
689761 return isrequired
690762end
@@ -768,13 +840,14 @@ end
768840# The basic algorithm is based on what was proposed in [^Wei84]. If there is even one active
769841# block in the blocks reachable from a conditional branch up to its successors' nearest
770842# 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
772844# from the conditional branch to the nearest common post-dominator.
773845#
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`.
778851#
779852# [Wei84]: M. Weiser, "Program Slicing," IEEE Transactions on Software Engineering, 10, pages 352-357, July 1984.
780853function add_control_flow! (isrequired, src:: CodeInfo , cfg:: CFG , postdomtree)
@@ -849,8 +922,8 @@ function reachable_blocks(cfg, from_bb::Int, to_bb::Int)
849922 return visited
850923end
851924
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 )
854927 changed = false
855928 for bbidx = 1 : length (cfg. blocks)
856929 if bbidx ∉ dead_blocks
@@ -868,7 +941,7 @@ function add_active_gotos!(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
868941end
869942
870943# 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 )
872945 dead_blocks = BitSet ()
873946 for bbidx = 1 : length (cfg. blocks)
874947 bb = cfg. blocks[bbidx]
@@ -889,13 +962,31 @@ function compute_dead_blocks(isrequired, src::CodeInfo, cfg::CFG, postdomtree)
889962 end
890963 if ! is_𝑰𝑵𝑭𝑳_active
891964 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
892970 end
893971 end
894972 end
895973 end
896974 return dead_blocks
897975end
898976
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+
899990# Do a traveral of "numbered" predecessors and find statement ranges and names of type definitions
900991function find_typedefs (src:: CodeInfo )
901992 typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
@@ -1018,26 +1109,19 @@ function add_inplace!(isrequired, src, edges, norequire)
10181109 return changed
10191110end
10201111
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
10371112function JuliaInterpreter. step_expr! (interp:: SelectiveInterpreter , frame:: Frame , istoplevel:: Bool )
10381113 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
10391123 if interp. isrequired[pc]
1040- step_expr! (interp. inner, frame:: Frame , istoplevel:: Bool )
1124+ step_expr! (interp. inner, frame, istoplevel)
10411125 else
10421126 next_or_nothing! (interp, frame)
10431127 end
@@ -1068,12 +1152,23 @@ See [`selective_eval_fromstart!`](@ref) to have that performed automatically.
10681152
10691153`isrequired` pertains only to `frame` itself, not any of its callees.
10701154
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+
10711164This will return either a `BreakpointRef`, the value obtained from the last executed statement
10721165(if stored to `frame.framedata.ssavlues`), or `nothing`.
10731166Typically, assignment to a variable binding does not result in an ssa store by JuliaInterpreter.
10741167"""
1168+ selective_eval! (interp:: SelectiveInterpreter , frame:: Frame , istoplevel:: Bool = false ) =
1169+ JuliaInterpreter. finish_and_return! (interp, frame, istoplevel)
10751170selective_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)
10771172selective_eval! (args... ) = selective_eval! (RecursiveInterpreter (), args... )
10781173
10791174"""
0 commit comments