LoweredCodeUtils.jl icon indicating copy to clipboard operation
LoweredCodeUtils.jl copied to clipboard

proper termination, take 2

Open aviatesk opened this issue 1 year ago • 1 comments

This PR is an alternative to JuliaDebug/LoweredCodeUtils.jl#99. This is built on top of JuliaDebug/LoweredCodeUtils.jl#116.

With this PR, the following test cases now pass correctly:

    # Final block is not a `return`: Need to use `controller::SelectiveEvalController` 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)
    controller = SelectiveEvalController()
    isrequired = lines_required(GlobalRef(ModSelective, :x), src, edges, controller)
    selective_eval_fromstart!(controller, 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 JuliaDebug/LoweredCodeUtils.jl#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 or lines_required! 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.

aviatesk avatar Sep 10 '24 16:09 aviatesk

Documenter test should be fixed before merging, though

timholy avatar Aug 09 '25 09:08 timholy