Skip to content

Fix a precision issue in abstract_iteration #41839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
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
35 changes: 25 additions & 10 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -847,9 +847,11 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n
return ret, AbstractIterationInfo(calls)
end
if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).MAX_TUPLE_SPLAT
stateordonet = stateordonet_widened
break
end
if !isa(stateordonet_widened, DataType) || !(stateordonet_widened <: Tuple) || isvatuple(stateordonet_widened) || length(stateordonet_widened.parameters) != 2
stateordonet = stateordonet_widened
break
end
nstatetype = getfield_tfunc(stateordonet, Const(2))
Expand All @@ -867,27 +869,40 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n
end
# From here on, we start asking for results on the widened types, rather than
# the precise (potentially const) state type
statetype = widenconst(statetype)
valtype = widenconst(valtype)
# statetype and valtype are reinitialized in the first iteration below from the
# (widened) stateordonet, which has not yet been fully analyzed in the loop above
statetype = Bottom
valtype = Bottom
may_have_terminated = Nothing <: stateordonet
while valtype !== Any
stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], sv).rt
stateordonet = widenconst(stateordonet)
nounion = typesubtract(stateordonet, Nothing, 0)
if !isa(nounion, DataType) || !(nounion <: Tuple) || isvatuple(nounion) || length(nounion.parameters) != 2
nounion = typeintersect(stateordonet, Tuple{Any,Any})
if nounion !== Union{} && !isa(nounion, DataType)
# nounion is of a type we cannot handle
valtype = Any
break
end
if nounion.parameters[1] <: valtype && nounion.parameters[2] <: statetype
if nounion === Union{} || (nounion.parameters[1] <: valtype && nounion.parameters[2] <: statetype)
# reached a fixpoint or iterator failed/gave invalid answer
if typeintersect(stateordonet, Nothing) === Union{}
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
if typeintersect(stateordonet, Nothing) === Union{}
if !(Nothing <: stateordonet)

As Nothing is a leaf type, this should be equal but more efficient?

Copy link
Member

@vtjnash vtjnash Aug 20, 2021

Choose a reason for hiding this comment

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

It should be almost identical (since typeintersect can check that property too), so not worth worrying about the difference too much

# Reached a fixpoint, but Nothing is not possible => iterator is infinite or failing
return Any[Bottom], nothing
# ... but cannot terminate
if !may_have_terminated
# ... and cannot have terminated prior to this loop
return Any[Bottom], nothing
else
# iterator may have terminated prior to this loop, but not during it
valtype = Bottom
end
end
break
end
valtype = tmerge(valtype, nounion.parameters[1])
statetype = tmerge(statetype, nounion.parameters[2])
stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], sv).rt
stateordonet = widenconst(stateordonet)
end
if valtype !== Union{}
push!(ret, Vararg{valtype})
end
push!(ret, Vararg{valtype})
return ret, nothing
end

Expand Down
17 changes: 16 additions & 1 deletion test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2872,9 +2872,24 @@ partial_return_2(x) = Val{partial_return_1(x)[2]}

@test Base.return_types(partial_return_2, (Int,)) == Any[Type{Val{1}}]

# Precision of abstract_iteration
# Soundness and precision of abstract_iteration
f41839() = (1:100...,)
@test NTuple{100,Int} <: only(Base.return_types(f41839, ())) <: Tuple{Vararg{Int}}
f_splat(x) = (x...,)
@test Base.return_types(f_splat, (Pair{Int,Int},)) == Any[Tuple{Int, Int}]
@test Base.return_types(f_splat, (UnitRange{Int},)) == Any[Tuple{Vararg{Int}}]
struct Itr41839_1 end # empty or infinite
Base.iterate(::Itr41839_1) = rand(Bool) ? (nothing, nothing) : nothing
Base.iterate(::Itr41839_1, ::Nothing) = (nothing, nothing)
@test Base.return_types(f_splat, (Itr41839_1,)) == Any[Tuple{}]
struct Itr41839_2 end # empty or failing
Base.iterate(::Itr41839_2) = rand(Bool) ? (nothing, nothing) : nothing
Base.iterate(::Itr41839_2, ::Nothing) = error()
@test Base.return_types(f_splat, (Itr41839_2,)) == Any[Tuple{}]
struct Itr41839_3 end
Base.iterate(::Itr41839_3 ) = rand(Bool) ? nothing : (nothing, 1)
Base.iterate(::Itr41839_3 , i) = i < 16 ? (i, i + 1) : nothing
@test only(Base.return_types(f_splat, (Itr41839_3,))) <: Tuple{Vararg{Union{Nothing, Int}}}

# issue #32699
f32699(a) = (id = a[1],).id
Expand Down