Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 115 additions & 82 deletions Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down