Skip to content

Commit

Permalink
inference: Fix correctness and ensure termination in the presence of …
Browse files Browse the repository at this point in the history
…PhiNodes (#53876)

There's two related, but distinct, issues here:
1. We were not using `tmerge` for merging SSA results inside loops,
which could cause infinite looping. In the absence of PhiNodes, things
usually have to go through a slot to be able to make the round trip,
which would usually put a PhiNode on the path, but it's possible there
may be other ways to smuggle things around (e.g. through exception
handling).

2. We were not properly accounting for the fact that PhiNode uses do not
need to be linearly ordered in the same BB, so we were getting the type
of the testcase here incorrect by failing to re-schedule the PhiNode.

The first of these shows up in the Diffractor test suite, the second was
found by writing the test case.
  • Loading branch information
Keno authored Mar 28, 2024
1 parent 4ee1022 commit e07c0f1
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 6 deletions.
12 changes: 10 additions & 2 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2731,7 +2731,8 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un
val = phi.values[i]
# N.B.: Phi arguments are restricted to not have effects, so we can drop
# them here safely.
rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv).rt)
thisval = abstract_eval_special_value(interp, val, vtypes, sv).rt
rt = tmerge(typeinf_lattice(interp), rt, thisval)
end
return rt
end
Expand All @@ -2745,7 +2746,14 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
if !isa(e, Expr)
if isa(e, PhiNode)
add_curr_ssaflag!(sv, IR_FLAGS_REMOVABLE)
return RTEffects(abstract_eval_phi(interp, e, vtypes, sv), Union{}, EFFECTS_TOTAL)
# 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, e, vtypes, sv)
old_rt = sv.ssavaluetypes[sv.currpc]
rt = old_rt === NOT_FOUND ? rt : tmerge(typeinf_lattice(interp), old_rt, rt)
return RTEffects(rt, Union{}, EFFECTS_TOTAL)
end
(; rt, exct, effects) = abstract_eval_special_value(interp, e, vtypes, sv)
else
Expand Down
5 changes: 1 addition & 4 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -691,10 +691,7 @@ function record_ssa_assign!(𝕃ᵢ::AbstractLattice, ssa_id::Int, @nospecialize
for r in frame.ssavalue_uses[ssa_id]
if was_reached(frame, r)
usebb = block_for_inst(frame.cfg, r)
# We're guaranteed to visit the statement if it's in the current
# basic block, since SSA values can only ever appear after their
# def.
if usebb != frame.currbb
if usebb != frame.currbb || r < ssa_id
push!(W, usebb)
end
end
Expand Down
28 changes: 28 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5652,3 +5652,31 @@ let t = ntuple(i -> i % 8 == 1 ? Int64 : Float64, 4000)
@test only(Base.return_types(Base.promote_typeof, t)) == Type{Float64}
@test only(Base.return_types(vcat, t)) == Vector{Float64}
end

# Infinite loop in inference on SSA assignment
const stop_infinite_loop::Base.Threads.Atomic{Bool} = Base.Threads.Atomic{Bool}(false)
function gen_infinite_loop_ssa_generator(world::UInt, source, _)
ci = make_codeinfo(Any[
# Block 1
(),
# Block 2
PhiNode(Int32[1, 5], Any[SSAValue(1), SSAValue(3)]),
Expr(:call, tuple, SSAValue(2)),
Expr(:call, getindex, GlobalRef(@__MODULE__, :stop_infinite_loop)),
GotoIfNot(SSAValue(4), 2),
# Block 3
ReturnNode(SSAValue(2))
]; slottypes=Any[Any])
ci.slotnames = Symbol[:var"#self#"]
ci
end

@eval function gen_infinite_loop_ssa()
$(Expr(:meta, :generated, gen_infinite_loop_ssa_generator))
$(Expr(:meta, :generated_only))
#= no body =#
end

# We want to make sure that both this returns `Tuple` and that
# it doesn't infinite loop inside inference.
@test Core.Compiler.return_type(gen_infinite_loop_ssa, Tuple{}) === Tuple

4 comments on commit e07c0f1

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily package evaluation, I will reply here when finished:

@nanosoldier runtests(isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

@vtjnash
Copy link
Member

@vtjnash vtjnash commented on e07c0f1 Apr 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here.

Please sign in to comment.