This PR is an alternative to #99.
This is built on top of #116.
With this PR, the following test cases now pass correctly:
```julia
# Final block is not a `return`: Need to use `config::SelectiveEvalRecurse` explicitly
ex = quote
x = 1
yy = 7
@Label loop
x += 1
x < 5 || return yy
@goto loop
end
frame = Frame(ModSelective, ex)
src = frame.framecode.src
edges = CodeEdges(ModSelective, src)
config = SelectiveEvalRecurse()
isrequired = lines_required(GlobalRef(ModSelective, :x), src, edges, config)
selective_eval_fromstart!(config, frame, isrequired, true)
@test ModSelective.x == 5
@test !isdefined(ModSelective, :yy)
```
The basic approach is overloading `JuliaInterpreter.step_expr!` and
`LoweredCodeUtils.next_or_nothing!` for the new `SelectiveEvalController`
type, as described below, to perform correct selective execution.
When `SelectiveEvalController` is passed as the `recurse` argument of
`selective_eval!`, the selective execution is adjusted as follows:
- **Implicit return**: In Julia's IR representation (`CodeInfo`), the
final block does not necessarily return and may `goto` another block.
And if the `return` statement is not included in the slice in such
cases, it is necessary to terminate `selective_eval!` when execution
reaches such implicit return statements. `controller.implicit_returns`
records the PCs of such return statements, and `selective_eval!` will
return when reaching those statements.
This is the core part of the fix for the test cases in
#99.
- **CFG short-cut**: When the successors of a conditional branch are
inactive, and it is safe to move the program counter from the
conditional branch to the nearest common post-dominator of those
successors, this short-cut is taken. This short-cut is not merely an
optimization but is actually essential for the correctness of the
selective execution. This is because, in `CodeInfo`, even if we simply
fall-through dead blocks (i.e., increment the program counter without
executing the statements of those blocks), it does not necessarily
lead to the nearest common post-dominator block.
And now [`lines_required`](@ref) or [`lines_required!`](@ref) will
update the `SelectiveEvalController` passed as their argument to be
appropriate for the program slice generated.
One thing to note is that currently, the `controller` is not be recursed.
That said, in Revise, which is the main consumer of LCU, there is no
need for recursive selective execution, and so `selective_eval!` does
not provide a system for inter-procedural selective evaluation.
Accordingly `SelectiveEvalController` does not recurse too, but this can
be left as a future extension.