diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 34bf08731b239..97c68d48f34ef 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3706,6 +3706,107 @@ function abstract_eval_ssavalue(s::SSAValue, ssavaluetypes::Vector{Any}) return typ end +struct AbstractEvalBasicStatementResult + rt + exct + effects::Union{Nothing,Effects} + changes::Union{Nothing,StateUpdate} + refinements # ::Union{Nothing,SlotRefinement,Vector{Any}} + currsaw_latestworld::Bool + function AbstractEvalBasicStatementResult(rt, exct, effects::Union{Nothing,Effects}, + changes::Union{Nothing,StateUpdate}, refinements, currsaw_latestworld::Bool) + @nospecialize rt exct refinements + return new(rt, exct, effects, changes, refinements, currsaw_latestworld) + end +end + +function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), sstate::StatementState, frame::InferenceState, + result::Union{Nothing,Future{RTEffects}}=nothing) + rt = nothing + exct = Bottom + changes = nothing + refinements = nothing + effects = nothing + currsaw_latestworld = sstate.saw_latestworld + if result !== nothing + @goto injectresult + end + if isa(stmt, NewvarNode) + changes = StateUpdate(stmt.slot, VarState(Bottom, true)) + elseif isa(stmt, PhiNode) + add_curr_ssaflag!(frame, IR_FLAGS_REMOVABLE) + # Implement convergence for PhiNodes. In particular, PhiNodes need to tmerge over + # the incoming values from all iterations, but `abstract_eval_phi` will only tmerge + # over the first and last iterations. By tmerging in the current old_rt, we ensure that + # we will not lose an intermediate value. + rt = abstract_eval_phi(interp, stmt, sstate, frame) + old_rt = frame.ssavaluetypes[frame.currpc] + rt = old_rt === NOT_FOUND ? rt : tmerge(typeinf_lattice(interp), old_rt, rt) + else + lhs = nothing + if isexpr(stmt, :(=)) + lhs = stmt.args[1] + stmt = stmt.args[2] + end + if !isa(stmt, Expr) + (; rt, exct, effects, refinements) = abstract_eval_special_value(interp, stmt, sstate, frame) + else + hd = stmt.head + if hd === :method + fname = stmt.args[1] + if isa(fname, SlotNumber) + changes = StateUpdate(fname, VarState(Any, false)) + end + elseif (hd === :code_coverage_effect || + # :boundscheck can be narrowed to Bool + (hd !== :boundscheck && is_meta_expr(stmt))) + rt = Nothing + elseif hd === :latestworld + currsaw_latestworld = true + rt = Nothing + else + result = abstract_eval_statement_expr(interp, stmt, sstate, frame)::Future{RTEffects} + if !isready(result) || !isempty(frame.tasks) + return result + + @label injectresult + # reload local variables + lhs = nothing + if isexpr(stmt, :(=)) + lhs = stmt.args[1] + stmt = stmt.args[2] + end + end + result = result[] + (; rt, exct, effects, refinements) = result + if effects.noub === NOUB_IF_NOINBOUNDS + if has_curr_ssaflag(frame, IR_FLAG_INBOUNDS) + effects = Effects(effects; noub=ALWAYS_FALSE) + elseif !propagate_inbounds(frame) + # The callee read our inbounds flag, but unless we propagate inbounds, + # we ourselves don't read our parent's inbounds. + effects = Effects(effects; noub=ALWAYS_TRUE) + end + end + @assert !isa(rt, TypeVar) "unhandled TypeVar" + rt = maybe_singleton_const(rt) + if !isempty(frame.pclimitations) + if rt isa Const || rt === Union{} + empty!(frame.pclimitations) + else + rt = LimitedAccuracy(rt, frame.pclimitations) + frame.pclimitations = IdSet{InferenceState}() + end + end + end + end + if lhs !== nothing && rt !== Bottom + changes = StateUpdate(lhs::SlotNumber, VarState(rt, false)) + end + end + return AbstractEvalBasicStatementResult(rt, exct, effects, changes, refinements, currsaw_latestworld) +end + struct BestguessInfo{Interp<:AbstractInterpreter} interp::Interp bestguess @@ -3986,14 +4087,16 @@ end # make as much progress on `frame` as possible (without handling cycles) struct CurrentState - result::Future + result::Future{RTEffects} currstate::VarTable currsaw_latestworld::Bool bbstart::Int bbend::Int - CurrentState(result::Future, currstate::VarTable, currsaw_latestworld::Bool, bbstart::Int, bbend::Int) = new(result, currstate, currsaw_latestworld, bbstart, bbend) + CurrentState(result::Future{RTEffects}, currstate::VarTable, currsaw_latestworld::Bool, bbstart::Int, bbend::Int) = + new(result, currstate, currsaw_latestworld, bbstart, bbend) CurrentState() = new() end + function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextresult::CurrentState) @assert !is_inferred(frame) W = frame.ip @@ -4012,7 +4115,9 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr bbend = nextresult.bbend currstate = nextresult.currstate currsaw_latestworld = nextresult.currsaw_latestworld - @goto injectresult + stmt = frame.src.code[currpc] + result = abstract_eval_basic_statement(interp, stmt, StatementState(currstate, currsaw_latestworld), frame, nextresult.result) + @goto injected_result end if currbb != 1 @@ -4165,87 +4270,15 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr end # Process non control-flow statements @assert isempty(frame.tasks) - rt = nothing - exct = Bottom - changes = nothing - refinements = nothing - effects = nothing - if isa(stmt, NewvarNode) - changes = StateUpdate(stmt.slot, VarState(Bottom, true)) - elseif isa(stmt, PhiNode) - add_curr_ssaflag!(frame, IR_FLAGS_REMOVABLE) - # Implement convergence for PhiNodes. In particular, PhiNodes need to tmerge over - # the incoming values from all iterations, but `abstract_eval_phi` will only tmerge - # over the first and last iterations. By tmerging in the current old_rt, we ensure that - # we will not lose an intermediate value. - rt = abstract_eval_phi(interp, stmt, StatementState(currstate, currsaw_latestworld), frame) - old_rt = frame.ssavaluetypes[currpc] - rt = old_rt === NOT_FOUND ? rt : tmerge(typeinf_lattice(interp), old_rt, rt) + sstate = StatementState(currstate, currsaw_latestworld) + result = abstract_eval_basic_statement(interp, stmt, sstate, frame) + if result isa Future{RTEffects} + return CurrentState(result, currstate, currsaw_latestworld, bbstart, bbend) else - lhs = nothing - if isexpr(stmt, :(=)) - lhs = stmt.args[1] - stmt = stmt.args[2] - end - if !isa(stmt, Expr) - (; rt, exct, effects, refinements) = abstract_eval_special_value(interp, stmt, StatementState(currstate, currsaw_latestworld), frame) - else - hd = stmt.head - if hd === :method - fname = stmt.args[1] - if isa(fname, SlotNumber) - changes = StateUpdate(fname, VarState(Any, false)) - end - elseif (hd === :code_coverage_effect || ( - hd !== :boundscheck && # :boundscheck can be narrowed to Bool - is_meta_expr(stmt))) - rt = Nothing - elseif hd === :latestworld - currsaw_latestworld = true - rt = Nothing - else - result = abstract_eval_statement_expr(interp, stmt, StatementState(currstate, currsaw_latestworld), frame)::Future - if !isready(result) || !isempty(frame.tasks) - return CurrentState(result, currstate, currsaw_latestworld, bbstart, bbend) - @label injectresult - # reload local variables - stmt = frame.src.code[currpc] - changes = nothing - lhs = nothing - if isexpr(stmt, :(=)) - lhs = stmt.args[1] - stmt = stmt.args[2] - end - result = nextresult.result::Future{RTEffects} - end - result = result[] - (; rt, exct, effects, refinements) = result - if effects.noub === NOUB_IF_NOINBOUNDS - if has_curr_ssaflag(frame, IR_FLAG_INBOUNDS) - effects = Effects(effects; noub=ALWAYS_FALSE) - elseif !propagate_inbounds(frame) - # The callee read our inbounds flag, but unless we propagate inbounds, - # we ourselves don't read our parent's inbounds. - effects = Effects(effects; noub=ALWAYS_TRUE) - end - end - @assert !isa(rt, TypeVar) "unhandled TypeVar" - rt = maybe_singleton_const(rt) - if !isempty(frame.pclimitations) - if rt isa Const || rt === Union{} - empty!(frame.pclimitations) - else - rt = LimitedAccuracy(rt, frame.pclimitations) - frame.pclimitations = IdSet{InferenceState}() - end - end - end - end - effects === nothing || merge_override_effects!(interp, effects, frame) - if lhs !== nothing && rt !== Bottom - changes = StateUpdate(lhs::SlotNumber, VarState(rt, false)) - end + @label injected_result + (; rt, exct, effects, changes, refinements, currsaw_latestworld) = result end + effects === nothing || merge_override_effects!(interp, effects, frame) if !has_curr_ssaflag(frame, IR_FLAG_NOTHROW) if exct !== Union{} update_exc_bestguess!(interp, exct, frame)