diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f3fc4e0423173..c307adbb93a4f 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -30,17 +30,17 @@ function propagate_conditional(rt::InterConditional, cond::Conditional) new_elsetype = rt.elsetype === Const(true) ? cond.thentype : cond.elsetype if rt.thentype == Bottom @assert rt.elsetype != Bottom - return Conditional(cond.slot, Bottom, new_elsetype) + return Conditional(cond.slot, cond.ssadef, Bottom, new_elsetype) elseif rt.elsetype == Bottom @assert rt.thentype != Bottom - return Conditional(cond.slot, new_thentype, Bottom) + return Conditional(cond.slot, cond.ssadef, new_thentype, Bottom) end - return Conditional(cond.slot, new_thentype, new_elsetype) + return Conditional(cond.slot, cond.ssadef, new_thentype, new_elsetype) end function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype), - sv::AbsIntState, max_methods::Int) + vtypes::Union{VarTable,Nothing}, sv::AbsIntState, max_methods::Int) π•ƒβ‚š, 𝕃ᡒ = ipo_lattice(interp), typeinf_lattice(interp) βŠ‘β‚š, βŠ”β‚š, βŠ”α΅’ = partialorder(π•ƒβ‚š), join(π•ƒβ‚š), join(𝕃ᡒ) argtypes = arginfo.argtypes @@ -221,7 +221,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end end - rettype = from_interprocedural!(interp, rettype, sv, arginfo, conditionals) + rettype = from_interprocedural!(interp, rettype, sv, arginfo, conditionals, vtypes) # Also considering inferring the compilation signature for this method, so # it is available to the compiler in case it ends up needing it. @@ -389,15 +389,15 @@ When we deal with multiple `MethodMatch`es, it's better to precompute `maybecond `tmerge`ing argument signature type of each method call. """ function from_interprocedural!(interp::AbstractInterpreter, @nospecialize(rt), sv::AbsIntState, - arginfo::ArgInfo, @nospecialize(maybecondinfo)) + arginfo::ArgInfo, @nospecialize(maybecondinfo), vtypes::Union{VarTable,Nothing}) rt = collect_limitations!(rt, sv) if isa(rt, InterMustAlias) - rt = from_intermustalias(typeinf_lattice(interp), rt, arginfo, sv) + rt = from_intermustalias(typeinf_lattice(interp), rt, arginfo, vtypes, sv) elseif is_lattice_bool(ipo_lattice(interp), rt) if maybecondinfo === nothing rt = widenconditional(rt) else - rt = from_interconditional(typeinf_lattice(interp), rt, sv, arginfo, maybecondinfo) + rt = from_interconditional(typeinf_lattice(interp), rt, sv, arginfo, maybecondinfo, vtypes) end end @assert !(rt isa InterConditional || rt isa InterMustAlias) "invalid lattice element returned from inter-procedural context" @@ -412,7 +412,7 @@ function collect_limitations!(@nospecialize(typ), sv::InferenceState) return typ end -function from_intermustalias(𝕃ᡒ::AbstractLattice, rt::InterMustAlias, arginfo::ArgInfo, sv::AbsIntState) +function from_intermustalias(𝕃ᡒ::AbstractLattice, rt::InterMustAlias, arginfo::ArgInfo, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) fargs = arginfo.fargs if fargs !== nothing && 1 ≀ rt.slot ≀ length(fargs) arg = ssa_def_slot(fargs[rt.slot], sv) @@ -420,7 +420,9 @@ function from_intermustalias(𝕃ᡒ::AbstractLattice, rt::InterMustAlias, argin argtyp = widenslotwrapper(arginfo.argtypes[rt.slot]) βŠ‘ = partialorder(𝕃ᡒ) if rt.vartyp βŠ‘ argtyp - return MustAlias(arg, rt.vartyp, rt.fldidx, rt.fldtyp) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(arg)] + return MustAlias(arg, vtyp.ssadef, rt.vartyp, rt.fldidx, rt.fldtyp) else # TODO optimize this case? end @@ -430,7 +432,7 @@ function from_intermustalias(𝕃ᡒ::AbstractLattice, rt::InterMustAlias, argin end function from_interconditional(𝕃ᡒ::AbstractLattice, @nospecialize(rt), sv::AbsIntState, - arginfo::ArgInfo, @nospecialize(maybecondinfo)) + arginfo::ArgInfo, @nospecialize(maybecondinfo), vtypes::Union{VarTable,Nothing}) has_conditional(𝕃ᡒ, sv) || return widenconditional(rt) (; fargs, argtypes) = arginfo fargs === nothing && return widenconditional(rt) @@ -512,7 +514,7 @@ function from_interconditional(𝕃ᡒ::AbstractLattice, @nospecialize(rt), sv:: if alias !== nothing return form_mustalias_conditional(alias, thentype, elsetype) end - return Conditional(slot, thentype, elsetype) # record a Conditional improvement to this slot + return Conditional(slot, vtypes[slot].ssadef, thentype, elsetype) # record a Conditional improvement to this slot end return widenconditional(rt) end @@ -1430,7 +1432,7 @@ function matching_cache_argtypes(𝕃::AbstractLattice, mi::MethodInstance, # TODO bail out here immediately rather than just propagating Bottom ? given_argtypes[i] = Bottom else - given_argtypes[i] = Conditional(slotid, thentype, elsetype) + given_argtypes[i] = Conditional(slotid, #= ssadef =# 0, thentype, elsetype) end continue end @@ -1508,7 +1510,7 @@ AbstractIterationResult(cti::Vector{Any}, info::MaybeAbstractIterationInfo) = # Union of Tuples of the same length is converted to Tuple of Unions. # returns an array of types function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(typ), - sv::AbsIntState) + vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if isa(typ, PartialStruct) widet = typ.typ if isa(widet, DataType) @@ -1594,19 +1596,20 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) end return AbstractIterationResult(Any[Vararg{eltype(tti0)}], nothing) else - return abstract_iteration(interp, itft, typ, sv) + return abstract_iteration(interp, itft, typ, vtypes, sv) end end # simulate iteration protocol on container type up to fixpoint -function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(itertype), sv::AbsIntState) +function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(itertype), + vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if isa(itft, Const) iteratef = itft.val else return AbstractIterationResult(Any[Vararg{Any}], nothing, Effects()) end @assert !isvarargtype(itertype) - call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true), sv) + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true), vtypes, sv) stateordonet = call.rt info = call.info # Return Bottom if this is not an iterator. @@ -1641,7 +1644,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n valtype = getfield_tfunc(𝕃ᡒ, stateordonet, Const(1)) push!(ret, valtype) statetype = nstatetype - call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), vtypes, sv) stateordonet = call.rt stateordonet_widened = widenconst(stateordonet) push!(calls, call) @@ -1675,7 +1678,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), vtypes, sv) push!(calls, call) stateordonet = call.rt stateordonet_widened = widenconst(stateordonet) @@ -1688,7 +1691,8 @@ end # do apply(af, fargs...), where af is a function value function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, - sv::AbsIntState, max_methods::Int=get_max_methods(interp, sv)) + vtypes::Union{VarTable,Nothing}, sv::AbsIntState, + max_methods::Int=get_max_methods(interp, sv)) itft = argtype_by_index(argtypes, 2) aft = argtype_by_index(argtypes, 3) (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) @@ -1713,9 +1717,9 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: infosβ€² = Vector{MaybeAbstractIterationInfo}[] for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]]) if !isvarargtype(ti) - (;cti, info, ai_effects) = precise_container_type(interp, itft, ti, sv) + (;cti, info, ai_effects) = precise_container_type(interp, itft, ti, vtypes, sv) else - (;cti, info, ai_effects) = precise_container_type(interp, itft, unwrapva(ti), sv) + (;cti, info, ai_effects) = precise_container_type(interp, itft, unwrapva(ti), vtypes, sv) # We can't represent a repeating sequence of the same types, # so tmerge everything together to get one type that represents # everything. @@ -1772,7 +1776,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: break end end - call = abstract_call(interp, ArgInfo(nothing, ct), si, sv, max_methods) + call = abstract_call(interp, ArgInfo(nothing, ct), si, vtypes, sv, max_methods) seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) res = tmerge(typeinf_lattice(interp), res, call.rt) @@ -1873,7 +1877,7 @@ end end function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs, argtypes)::ArgInfo, - sv::AbsIntState) + vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @nospecialize f la = length(argtypes) 𝕃ᡒ = typeinf_lattice(interp) @@ -1914,7 +1918,9 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs fldidx = maybe_const_fldidx(vartyp, a3.val) if fldidx !== nothing # wrap this aliasable field into `MustAlias` for possible constraint propagations - return MustAlias(var, vartyp, fldidx, rt) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(var)] + return MustAlias(var, vtyp.ssadef, vartyp, fldidx, rt) end end end @@ -1929,7 +1935,9 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs if isa(a, SlotNumber) cndt = isa_condition(a2, a3, InferenceParams(interp).max_union_splitting, rt) if cndt !== nothing - return Conditional(a, cndt.thentype, cndt.elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(a)] + return Conditional(a, vtyp.ssadef, cndt.thentype, cndt.elsetype) end end if isa(a2, MustAlias) @@ -1947,7 +1955,9 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs # !(x isa T) implies !(Type{a2} <: T) # TODO: complete splitting, based on which portions of the Union a3 for which isa_tfunc returns Const(true) or Const(false) instead of Bool elsetype = typesubtract(a3, Type{widenconst(a2)}, InferenceParams(interp).max_union_splitting) - return Conditional(b, a3, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(b)] + return Conditional(b, vtyp.ssadef, a3, elsetype) end end elseif f === (===) @@ -1958,16 +1968,20 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs # if doing a comparison to a singleton, consider returning a `Conditional` instead if isa(aty, Const) if isa(b, SlotNumber) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(b)] cndt = egal_condition(aty, bty, InferenceParams(interp).max_union_splitting, rt) - return Conditional(b, cndt.thentype, cndt.elsetype) + return Conditional(b, vtyp.ssadef, cndt.thentype, cndt.elsetype) elseif isa(bty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) cndt = egal_condition(aty, bty.fldtyp, InferenceParams(interp).max_union_splitting) return form_mustalias_conditional(bty, cndt.thentype, cndt.elsetype) end elseif isa(bty, Const) if isa(a, SlotNumber) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(a)] cndt = egal_condition(bty, aty, InferenceParams(interp).max_union_splitting, rt) - return Conditional(a, cndt.thentype, cndt.elsetype) + return Conditional(a, vtyp.ssadef, cndt.thentype, cndt.elsetype) elseif isa(aty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) cndt = egal_condition(bty, aty.fldtyp, InferenceParams(interp).max_union_splitting) return form_mustalias_conditional(aty, cndt.thentype, cndt.elsetype) @@ -1998,18 +2012,24 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs if isa(b, SlotNumber) thentype = rt === Const(false) ? Bottom : widenslotwrapper(bty) elsetype = rt === Const(true) ? Bottom : widenslotwrapper(bty) - return Conditional(b, thentype, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(b)] + return Conditional(b, vtyp.ssadef, thentype, elsetype) elseif isa(a, SlotNumber) thentype = rt === Const(false) ? Bottom : widenslotwrapper(aty) elsetype = rt === Const(true) ? Bottom : widenslotwrapper(aty) - return Conditional(a, thentype, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(a)] + return Conditional(a, vtyp.ssadef, thentype, elsetype) end elseif f === Core.Compiler.not_int aty = argtypes[2] if isa(aty, Conditional) thentype = rt === Const(false) ? Bottom : aty.elsetype elsetype = rt === Const(true) ? Bottom : aty.thentype - return Conditional(aty.slot, thentype, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(aty)] + return Conditional(aty.slot, vtyp.ssadef, thentype, elsetype) end elseif f === isdefined a = ssa_def_slot(fargs[2], sv) @@ -2032,7 +2052,9 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs elsetype = elsetype βŠ” ty end end - return Conditional(a, thentype, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(a)] + return Conditional(a, vtyp.ssadef, thentype, elsetype) else thentype = form_partially_defined_struct(argtype2, argtypes[3]) if thentype !== nothing @@ -2042,7 +2064,9 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs elseif rt === Const(true) elsetype = Bottom end - return Conditional(a, thentype, elsetype) + @assert vtypes !== nothing + vtyp = vtypes[slot_id(a)] + return Conditional(a, vtyp.ssadef, thentype, elsetype) end end end @@ -2127,7 +2151,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An return CallMeta(ret, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end -function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState) +function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) argtypes = arginfo.argtypes ftβ€² = argtype_by_index(argtypes, 2) ft = widenconst(ftβ€²) @@ -2178,7 +2202,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt (; rt, effects, const_result, edge) = const_call_result end end - rt = from_interprocedural!(interp, rt, sv, arginfo, sig) + rt = from_interprocedural!(interp, rt, sv, arginfo, sig, vtypes) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) return CallMeta(rt, Any, effects, info) @@ -2191,10 +2215,10 @@ function invoke_rewrite(xs::Vector{Any}) return newxs end -function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState) +function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, vtypes, sv::AbsIntState) if length(argtypes) == 3 finalizer_argvec = Any[argtypes[2], argtypes[3]] - call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), sv, #=max_methods=#1) + call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), vtypes, sv, #=max_methods=#1) return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end return CallMeta(Nothing, Any, Effects(), NoCallInfo()) @@ -2220,26 +2244,26 @@ end # call where the function is known exactly function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), - arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, - max_methods::Int = get_max_methods(interp, f, sv)) + arginfo::ArgInfo, si::StmtInfo, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState, max_methods::Int = get_max_methods(interp, f, sv)) (; fargs, argtypes) = arginfo la = length(argtypes) 𝕃ᡒ = typeinf_lattice(interp) if isa(f, Builtin) if f === _apply_iterate - return abstract_apply(interp, argtypes, si, sv, max_methods) + return abstract_apply(interp, argtypes, si, vtypes, sv, max_methods) elseif f === invoke - return abstract_invoke(interp, arginfo, si, sv) + return abstract_invoke(interp, arginfo, si, vtypes, sv) elseif f === modifyfield! || f === Core.modifyglobal! || f === Core.memoryrefmodify! || f === atomic_pointermodify - return abstract_modifyop!(interp, f, argtypes, si, sv) + return abstract_modifyop!(interp, f, argtypes, si, vtypes, sv) elseif f === Core.finalizer - return abstract_finalizer(interp, argtypes, sv) + return abstract_finalizer(interp, argtypes, vtypes, sv) elseif f === applicable return abstract_applicable(interp, argtypes, sv, max_methods) elseif f === throw return abstract_throw(interp, argtypes, sv) end - rt = abstract_call_builtin(interp, f, arginfo, sv) + rt = abstract_call_builtin(interp, f, arginfo, vtypes, sv) ft = popfirst!(argtypes) effects = builtin_effects(𝕃ᡒ, f, argtypes, rt) if effects.nothrow @@ -2280,7 +2304,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), resize!(T, la) atype = Tuple{T...} T[1] = Const(TypeVar) - abstract_call_gf_by_type(interp, f, ArgInfo(nothing, T), si, atype, sv, max_methods) + abstract_call_gf_by_type(interp, f, ArgInfo(nothing, T), si, atype, vtypes, sv, max_methods) end pT = typevar_tfunc(𝕃ᡒ, n, lb_var, ub_var) typevar_argtypes = Any[n, lb_var, ub_var] @@ -2292,7 +2316,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), end return CallMeta(pT, exct, effects, call.info) elseif f === UnionAll - call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods) + call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, vtypes, sv, max_methods) return abstract_call_unionall(interp, argtypes, call) elseif f === Tuple && la == 2 aty = argtypes[2] @@ -2304,10 +2328,11 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return return_type_tfunc(interp, argtypes, si, sv) elseif la == 3 && f === Core.:(!==) # mark !== as exactly a negated call to === - call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods) - rty = abstract_call_known(interp, (===), arginfo, si, sv, max_methods).rt + call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, vtypes, sv, max_methods) + rty = abstract_call_known(interp, (===), arginfo, si, vtypes, sv, max_methods).rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else + # TODO: verify + return CallMeta(Conditional(rty.slot, rty.ssadef, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else elseif isa(rty, Const) return CallMeta(Const(rty.val === false), Bottom, EFFECTS_TOTAL, MethodResultPure()) end @@ -2321,18 +2346,18 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), fargs = nothing end argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] - return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) + return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, vtypes, sv, max_methods) elseif la == 2 && f === Core.typename return CallMeta(typename_static(argtypes[2]), Bottom, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end atype = argtypes_to_type(argtypes) - return abstract_call_gf_by_type(interp, f, arginfo, si, atype, sv, max_methods) + return abstract_call_gf_by_type(interp, f, arginfo, si, atype, vtypes, sv, max_methods) end -function abstract_call_opaque_closure(interp::AbstractInterpreter, - closure::PartialOpaque, arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, check::Bool=true) +function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, + arginfo::ArgInfo, si::StmtInfo, vtypes::Union{VarTable,Nothing}, sv::AbsIntState, check::Bool=true) sig = argtypes_to_type(arginfo.argtypes) result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, si, sv) (; rt, edge, effects, volatile_inf_result) = result @@ -2359,7 +2384,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, effects = Effects(effects; nothrow=false) end end - rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) + rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types, vtypes) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) return CallMeta(rt, Any, effects, info) @@ -2376,13 +2401,13 @@ function most_general_argtypes(closure::PartialOpaque) end function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), - arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, - max_methods::Int) + arginfo::ArgInfo, si::StmtInfo, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState, max_methods::Int) if isa(ft, PartialOpaque) newargtypes = copy(arginfo.argtypes) newargtypes[1] = ft.env return abstract_call_opaque_closure(interp, - ft, ArgInfo(arginfo.fargs, newargtypes), si, sv, #=check=#true) + ft, ArgInfo(arginfo.fargs, newargtypes), si, vtypes, sv, #=check=#true) end wft = widenconst(ft) if hasintersect(wft, Builtin) @@ -2397,20 +2422,22 @@ function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), end # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic atype = argtypes_to_type(arginfo.argtypes) - return abstract_call_gf_by_type(interp, nothing, arginfo, si, atype, sv, max_methods) + return abstract_call_gf_by_type(interp, nothing, arginfo, si, atype, vtypes, sv, max_methods) end +# TODO: abstract + # call where the function is any lattice element function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, - sv::AbsIntState, max_methods::Int=typemin(Int)) + vtypes::Union{VarTable,Nothing}, sv::AbsIntState, max_methods::Int=typemin(Int)) ft = widenslotwrapper(arginfo.argtypes[1]) f = singleton_type(ft) if f === nothing max_methods = max_methods == typemin(Int) ? get_max_methods(interp, sv) : max_methods - return abstract_call_unknown(interp, ft, arginfo, si, sv, max_methods) + return abstract_call_unknown(interp, ft, arginfo, si, vtypes, sv, max_methods) end max_methods = max_methods == typemin(Int) ? get_max_methods(interp, f, sv) : max_methods - return abstract_call_known(interp, f, arginfo, si, sv, max_methods) + return abstract_call_known(interp, f, arginfo, si, vtypes, sv, max_methods) end function sp_type_rewrap(@nospecialize(T), mi::MethodInstance, isreturn::Bool) @@ -2468,7 +2495,7 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::U # this may be the wrong world for the call, # but some of the result is likely to be valid anyways # and that may help generate better codegen - abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false), sv) + abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false), vtypes, sv) rt = e.args[1] isa(rt, Type) || (rt = Any) return RTEffects(rt, Any, EFFECTS_UNKNOWN) @@ -2551,13 +2578,13 @@ struct RTEffects end end -function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) +function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, vtypes::Union{VarTable,Nothing}, sv::InferenceState) unused = call_result_unused(sv, sv.currpc) if unused add_curr_ssaflag!(sv, IR_FLAG_UNUSED) end si = StmtInfo(!unused) - call = abstract_call(interp, arginfo, si, sv) + call = abstract_call(interp, arginfo, si, vtypes, sv) sv.stmt_info[sv.currpc] = call.info return call end @@ -2570,7 +2597,7 @@ function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{ return RTEffects(Bottom, Any, Effects()) end arginfo = ArgInfo(ea, argtypes) - (; rt, exct, effects, refinements) = abstract_call(interp, arginfo, sv) + (; rt, exct, effects, refinements) = abstract_call(interp, arginfo, vtypes, sv) return RTEffects(rt, exct, effects, refinements) end @@ -2701,7 +2728,7 @@ function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, argtypes = most_general_argtypes(rt) pushfirst!(argtypes, rt.env) callinfo = abstract_call_opaque_closure(interp, rt, - ArgInfo(nothing, argtypes), StmtInfo(true), sv, #=check=#false) + ArgInfo(nothing, argtypes), StmtInfo(true), vtypes, sv, #=check=#false) sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) end end @@ -2734,7 +2761,7 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U elseif !vtyp.undef rt = Const(true) # definitely assigned previously else # form `Conditional` to refine `vtyp.undef` in the then branch - rt = Conditional(sym, vtyp.typ, vtyp.typ; isdefined=true) + rt = Conditional(sym, vtyp.ssadef, vtyp.typ, vtyp.typ; isdefined=true) end elseif isa(sym, GlobalRef) if InferenceParams(interp).assume_bindings_static @@ -3045,7 +3072,7 @@ end @nospecializeinfer function widenreturn(𝕃ᡒ::MustAliasesLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, MustAlias) - if 1 ≀ rt.slot ≀ info.nargs + if 1 ≀ rt.slot ≀ info.nargs && rt.ssadef == 0 rt = InterMustAlias(rt) else rt = widenmustalias(rt) @@ -3080,13 +3107,13 @@ end rt = widenconditional(rt) end end - if isa(rt, Conditional) + if isa(rt, Conditional) && rt.ssadef == 0 rt = InterConditional(rt.slot, rt.thentype, rt.elsetype) elseif is_lattice_bool(𝕃ᡒ, rt) rt = bool_rt_to_conditional(rt, info) end end - if isa(rt, Conditional) + if isa(rt, Conditional) && rt.ssadef == 0 rt = InterConditional(rt) end isa(rt, InterConditional) && return rt @@ -3207,7 +3234,7 @@ end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) - changes = StateUpdate(stmt.slot, VarState(Bottom, true)) + changes = StateUpdate(stmt.slot, VarState(Bottom, frame.currpc, #= undef =# true)) return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) @@ -3222,7 +3249,7 @@ end end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(rt, false)) + changes = StateUpdate(lhs, VarState(rt, frame.currpc, #= undef =# false)) elseif isa(lhs, GlobalRef) handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) @@ -3232,7 +3259,7 @@ end elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) - changes = StateUpdate(fname, VarState(Any, false)) + changes = StateUpdate(fname, VarState(Any, frame.currpc, #= undef =# false)) end return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( @@ -3253,14 +3280,17 @@ function update_bbstate!(𝕃ᡒ::AbstractLattice, frame::InferenceState, bb::In frame.bb_vartables[bb] = copy(vartable) return true else - return stupdate!(𝕃ᡒ, bbtable, vartable) + pc = first(frame.cfg.blocks[bb].stmts) + # Minus sign marks this as a "virtual" PC so that it is + # not confused with a real assignment at this PC. + return stupdate!(𝕃ᡒ, bbtable, vartable, -pc) end end function init_vartable!(vartable::VarTable, frame::InferenceState) nargtypes = length(frame.result.argtypes) for i = 1:length(vartable) - vartable[i] = VarState(Bottom, i > nargtypes) + vartable[i] = VarState(Bottom, #= ssadef =# typemin(Int), i > nargtypes) end return vartable end @@ -3390,9 +3420,10 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end orig_condt = condt if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condslot, SlotNumber) + vtyp = currstate[slot_id(condslot)] # if this non-`Conditional` object is a slot, we form and propagate # the conditional constraint on it - condt = Conditional(condslot, Const(true), Const(false)) + condt = Conditional(condslot, vtyp.ssadef, Const(true), Const(false)) end condval = maybe_extract_const_bool(condt) nothrow = (condval !== nothing) || βŠ‘(𝕃ᡒ, orig_condt, Bool) @@ -3437,27 +3468,27 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) # We continue with the true branch, but process the false # branch here. - if isa(condt, Conditional) - else_change = conditional_change(𝕃ᡒ, currstate, condt, #=then_or_else=#false) + if isa(condt, Conditional) && conditional_valid(condt, currstate) + else_change = conditional_change(𝕃ᡒ, currstate, condt, :else) if else_change !== nothing elsestate = copy(currstate) - stoverwrite1!(elsestate, else_change) + strefine1!(elsestate, else_change) elseif condslot isa SlotNumber elsestate = copy(currstate) else elsestate = currstate end if condslot isa SlotNumber # refine the type of this conditional object itself for this else branch - stoverwrite1!(elsestate, condition_object_change(currstate, condt, condslot, #=then_or_else=#false)) + strefine1!(elsestate, condition_object_change(currstate, condt, condslot, :else)) end else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, elsestate) - then_change = conditional_change(𝕃ᡒ, currstate, condt, #=then_or_else=#true) + then_change = conditional_change(𝕃ᡒ, currstate, condt, :then) thenstate = currstate if then_change !== nothing - stoverwrite1!(thenstate, then_change) + strefine1!(thenstate, then_change) end if condslot isa SlotNumber # refine the type of this conditional object itself for this then branch - stoverwrite1!(thenstate, condition_object_change(currstate, condt, condslot, #=then_or_else=#true)) + strefine1!(thenstate, condition_object_change(currstate, condt, condslot, :then)) end else else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, currstate) @@ -3578,15 +3609,24 @@ function apply_refinement!(𝕃ᡒ::AbstractLattice, slot::SlotNumber, @nospecia oldtyp = vtype.typ ⊏ = strictpartialorder(𝕃ᡒ) if newtyp ⊏ oldtyp - stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef)) - stoverwrite1!(currstate, stmtupdate) + refinement = StateRefinement(slot_id(slot), newtyp, vtype.undef) + strefine1!(currstate, refinement) end end -function conditional_change(𝕃ᡒ::AbstractLattice, currstate::VarTable, condt::Conditional, then_or_else::Bool) +function conditional_valid(condt::Conditional, currstate::VarTable) + @assert condt.ssadef != typemin(Int) + return currstate[condt.slot].ssadef == condt.ssadef +end + +function conditional_change(𝕃ᡒ::AbstractLattice, currstate::VarTable, condt::Conditional, then_or_else::Symbol) vtype = currstate[condt.slot] oldtyp = vtype.typ - newtyp = then_or_else ? condt.thentype : condt.elsetype + newtyp = if then_or_else === :then + condt.thentype + elseif then_or_else === :else + condt.elsetype + else @assert false end if iskindtype(newtyp) # this code path corresponds to the special handling for `isa(x, iskindtype)` check # implemented within `abstract_call_builtin` @@ -3603,17 +3643,20 @@ function conditional_change(𝕃ᡒ::AbstractLattice, currstate::VarTable, condt newtyp = tmerge(𝕃ᡒ, newtyp, LimitedAccuracy(Bottom, oldtyp.causes)) end # if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` information - newundef = condt.isdefined ? !then_or_else : vtype.undef - return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, newundef), #=conditional=#true) + newundef = condt.isdefined ? (then_or_else === :else) : vtype.undef + return StateRefinement(condt.slot, newtyp, newundef) end function condition_object_change(currstate::VarTable, condt::Conditional, - condslot::SlotNumber, then_or_else::Bool) + condslot::SlotNumber, then_or_else::Symbol) vtype = currstate[slot_id(condslot)] - newcondt = Conditional(condt.slot, - then_or_else ? condt.thentype : Union{}, - then_or_else ? Union{} : condt.elsetype) - return StateUpdate(condslot, VarState(newcondt, vtype.undef)) + (thentype, elsetype) = if then_or_else === :then + (condt.thentype, Union{}) + elseif then_or_else === :else + (Union{}, condt.elsetype) + else @assert false end + newcondt = Conditional(condt.slot, condt.ssadef, thentype, elsetype) + return StateRefinement(slot_id(condslot), newcondt, vtype.undef) end # make as much progress on `frame` as possible (by handling cycles) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 6953dea5b9bd7..50633a86aaddb 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -316,10 +316,10 @@ mutable struct InferenceState for i = 1:nslots argtyp = (i > nargtypes) ? Bottom : argtypes[i] if argtyp === Bool && has_conditional(typeinf_lattice(interp)) - argtyp = Conditional(i, Const(true), Const(false)) + argtyp = Conditional(i, #= ssadef =# 0, Const(true), Const(false)) end slottypes[i] = argtyp - bb_vartable1[i] = VarState(argtyp, i > nargtypes) + bb_vartable1[i] = VarState(argtyp, #= ssadef =# 0, i > nargtypes) end src.ssavaluetypes = ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] @@ -712,7 +712,7 @@ function sptypes_from_meth_instance(mi::MethodInstance) ty = Const(v) undef = false end - sptypes[i] = VarState(ty, undef) + sptypes[i] = VarState(ty, typemin(Int), undef) end return sptypes end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index fb712b1c71b12..27a4ffa4d08f6 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -197,7 +197,7 @@ function OptimizationState(mi::MethodInstance, src::CodeInfo, interp::AbstractIn bb_vartables = Union{VarTable,Nothing}[] for block = 1:length(cfg.blocks) push!(bb_vartables, VarState[ - VarState(slottypes[slot], src.slotflags[slot] & SLOT_USEDUNDEF != 0) + VarState(slottypes[slot], typemin(Int), src.slotflags[slot] & SLOT_USEDUNDEF != 0) for slot = 1:nslots ]) end diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 1aeb87accbcd7..204b4bb63bfdd 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -49,9 +49,9 @@ function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int return abstract_eval_phi(interp, phi, nothing, irsv) end -function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState) +function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, vtypes::Union{VarTable,Nothing}, irsv::IRInterpretationState) si = StmtInfo(true) # TODO better job here? - call = abstract_call(interp, arginfo, si, irsv) + call = abstract_call(interp, arginfo, si, vtypes, irsv) irsv.ir.stmts[irsv.curridx][:info] = call.info return call end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 0c57c04a6ddea..f64ebd45cdbb7 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -229,7 +229,7 @@ end function not_tfunc(𝕃::AbstractLattice, @nospecialize(b)) if isa(b, Conditional) - return Conditional(b.slot, b.elsetype, b.thentype) + return Conditional(b.slot, b.ssadef, b.elsetype, b.thentype) elseif isa(b, Const) return Const(not_int(b.val)) end @@ -350,14 +350,14 @@ end if isa(x, Conditional) y = widenconditional(y) if isa(y, Const) - y.val === false && return Conditional(x.slot, x.elsetype, x.thentype) + y.val === false && return Conditional(x.slot, x.ssadef, x.elsetype, x.thentype) y.val === true && return x return Const(false) end elseif isa(y, Conditional) x = widenconditional(x) if isa(x, Const) - x.val === false && return Conditional(y.slot, y.elsetype, y.thentype) + x.val === false && return Conditional(y.slot, y.ssadef, y.elsetype, y.thentype) x.val === true && return y return Const(false) end @@ -1355,7 +1355,7 @@ end return Bool end -@nospecs function abstract_modifyop!(interp::AbstractInterpreter, ff, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) +@nospecs function abstract_modifyop!(interp::AbstractInterpreter, ff, argtypes::Vector{Any}, si::StmtInfo, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if ff === modifyfield! minargs = 5 maxargs = 6 @@ -1415,7 +1415,7 @@ end # as well as compute the info for the method matches op = unwrapva(argtypes[op_argi]) v = unwrapva(argtypes[v_argi]) - callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), sv, #=max_methods=#1) + callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), vtypes, sv, #=max_methods=#1) TF2 = tmeet(callinfo.rt, widenconst(TF)) if TF2 === Bottom RT = Bottom @@ -2931,10 +2931,11 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s if isa(sv, InferenceState) old_restrict = sv.restrict_abstract_call_sites sv.restrict_abstract_call_sites = false - call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, #=max_methods=#-1) + # TODO: vtypes? + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, nothing, sv, #=max_methods=#-1) sv.restrict_abstract_call_sites = old_restrict else - call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, #=max_methods=#-1) + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, nothing, sv, #=max_methods=#-1) end info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure() rt = widenslotwrapper(call.rt) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index e2f2a1f2cc975..b8703558e3414 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -744,7 +744,7 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState) for slot in 1:nslots vt = varstate[slot] widened_type = widenslotwrapper(ignorelimited(vt.typ)) - varstate[slot] = VarState(widened_type, vt.undef) + varstate[slot] = VarState(widened_type, vt.ssadef, vt.undef) end end end diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 86fa8af21615f..0152d87426685 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -60,7 +60,7 @@ the type of `SlotNumber(cnd.slot)` will be limited by `cnd.thentype` and in the false branch, it will be limited by `cnd.elsetype`. Example: ```julia -let cond = isa(x::Union{Int, Float}, Int)::Conditional(x, Int, Float) +let cond = isa(x::Union{Int, Float}, Int)::Conditional(x, _, Int, Float) if cond # May assume x is `Int` now else @@ -71,21 +71,22 @@ end """ struct Conditional slot::Int + ssadef::Int thentype elsetype # `isdefined` indicates this `Conditional` is from `@isdefined slot`, implying that # the `undef` information of `slot` can be improved in the then branch. # Since this is only beneficial for local inference, it is not translated into `InterConditional`. isdefined::Bool - function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype); + function Conditional(slot::Int, ssadef::Int, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) assert_nested_slotwrapper(thentype) assert_nested_slotwrapper(elsetype) - return new(slot, thentype, elsetype, isdefined) + return new(slot, ssadef, thentype, elsetype, isdefined) end end -Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) = - Conditional(slot_id(var), thentype, elsetype; isdefined) +Conditional(var::SlotNumber, ssadef::Int, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) = + Conditional(slot_id(var), ssadef, thentype, elsetype; isdefined) import Core: InterConditional """ @@ -105,8 +106,10 @@ InterConditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetyp InterConditional(slot_id(var), thentype, elsetype) const AnyConditional = Union{Conditional,InterConditional} -Conditional(cnd::InterConditional) = Conditional(cnd.slot, cnd.thentype, cnd.elsetype) -InterConditional(cnd::Conditional) = InterConditional(cnd.slot, cnd.thentype, cnd.elsetype) +function InterConditional(cnd::Conditional) + @assert cnd.ssadef == 0 + InterConditional(cnd.slot, cnd.thentype, cnd.elsetype) +end """ alias::MustAlias @@ -133,19 +136,20 @@ N.B. currently this lattice element is only used in abstractinterpret, not in op """ struct MustAlias slot::Int + ssadef::Int vartyp::Any fldidx::Int fldtyp::Any - function MustAlias(slot::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) + function MustAlias(slot::Int, ssadef::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) assert_nested_slotwrapper(vartyp) assert_nested_slotwrapper(fldtyp) # @assert !isalreadyconst(vartyp) "vartyp is already const" # @assert !isalreadyconst(fldtyp) "fldtyp is already const" - return new(slot, vartyp, fldidx, fldtyp) + return new(slot, ssadef, vartyp, fldidx, fldtyp) end end -MustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) = - MustAlias(slot_id(var), vartyp, fldidx, fldtyp) +MustAlias(var::SlotNumber, ssadef::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) = + MustAlias(slot_id(var), ssadef, vartyp, fldidx, fldtyp) """ alias::InterMustAlias @@ -169,8 +173,10 @@ InterMustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecializ InterMustAlias(slot_id(var), vartyp, fldidx, fldtyp) const AnyMustAlias = Union{MustAlias,InterMustAlias} -MustAlias(alias::InterMustAlias) = MustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp) -InterMustAlias(alias::MustAlias) = InterMustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp) +function InterMustAlias(alias::MustAlias) + @assert alias.ssadef == 0 + InterMustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp) +end struct PartialTypeVar tv::TypeVar @@ -184,8 +190,20 @@ end struct StateUpdate var::SlotNumber vtype::VarState - conditional::Bool - StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional) +end + +""" +Similar to `StateUpdate`, except with the additional guarantee that object identity +is preserved by the update (i.e. `x (before) === x (after)`). +""" +struct StateRefinement + slot::Int + # XXX: This should be an intersection of the old type with the new + # (i.e. newtyp βŠ‘ oldtyp) + newtyp + undef::Bool + + StateRefinement(slot::Int, @nospecialize(newtyp), undef::Bool) = new(slot, newtyp, undef) end """ @@ -328,6 +346,7 @@ end return false end +is_same_conditionals(a::Conditional, b::Conditional) = a.slot == b.slot && a.ssadef == b.ssadef is_same_conditionals(a::C, b::C) where C<:AnyConditional = a.slot == b.slot @nospecializeinfer is_lattice_bool(lattice::AbstractLattice, @nospecialize(typ)) = typ !== Bottom && βŠ‘(lattice, typ, Bool) @@ -376,7 +395,7 @@ end end @nospecializeinfer function form_mustalias_conditional(alias::MustAlias, @nospecialize(thentype), @nospecialize(elsetype)) - (; slot, vartyp, fldidx) = alias + (; slot, ssadef, vartyp, fldidx) = alias if isa(vartyp, PartialStruct) fields = vartyp.fields thenfields = thentype === Bottom ? nothing : copy(fields) @@ -387,7 +406,7 @@ end elsefields === nothing || (elsefields[i] = elsetype) end end - return Conditional(slot, + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(vartyp.typ, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp.typ, elsefields)) else @@ -404,7 +423,7 @@ end elsefields === nothing || push!(elsefields, t) end end - return Conditional(slot, + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(vartyp_widened, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp_widened, elsefields)) end @@ -745,34 +764,39 @@ widenconst(::LimitedAccuracy) = error("unhandled LimitedAccuracy") # state management # #################### -function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}) +function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}, join_pc::Int) sa === sb && return sa sa === NOT_FOUND && return sb sb === NOT_FOUND && return sa - return VarState(tmerge(lattice, sa.typ, sb.typ), sa.undef | sb.undef) + return VarState(tmerge(lattice, sa.typ, sb.typ), sa.ssadef == sb.ssadef ? sa.ssadef : join_pc, sa.undef | sb.undef) end -@nospecializeinfer @inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o)) = - (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && βŠ‘(lattice, n.typ, o.typ)))) +@nospecializeinfer @inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o), join_pc::Int) = + (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && (n.ssadef == o.ssadef || o.ssadef == join_pc) && βŠ‘(lattice, n.typ, o.typ)))) # remove any lattice elements that wrap the reassigned slot object from the vartable -function invalidate_slotwrapper(vt::VarState, changeid::Int, ignore_conditional::Bool) +function invalidate_slotwrapper(vt::VarState, changeid::Int) newtyp = ignorelimited(vt.typ) - if (!ignore_conditional && isa(newtyp, Conditional) && newtyp.slot == changeid) || - (isa(newtyp, MustAlias) && newtyp.slot == changeid) + if ((isa(newtyp, Conditional) && newtyp.slot == changeid) || + (isa(newtyp, MustAlias) && newtyp.slot == changeid)) newtyp = @noinline widenwrappedslotwrapper(vt.typ) - return VarState(newtyp, vt.undef) + return VarState(newtyp, vt.ssadef, vt.undef) end return nothing end -function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable) +function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable, join_pc::Int) changed = false for i = 1:length(state) newtype = changes[i] oldtype = state[i] - if schanged(lattice, newtype, oldtype) - state[i] = smerge(lattice, oldtype, newtype) + # In addition to computing the type, the merge here computes the "reaching definition" + # for a slot. The provided `join_pc` is a "virtual" PC, which corresponds to the Ο•-block + # that would exist at the beginning of the BasicBlock. + # + # This effectively applies the "path-convergence criterion" for SSA construction. + if schanged(lattice, newtype, oldtype, join_pc) + state[i] = smerge(lattice, oldtype, newtype, join_pc) changed = true end end @@ -789,7 +813,7 @@ end function stoverwrite1!(state::VarTable, change::StateUpdate) changeid = slot_id(change.var) for i = 1:length(state) - invalidated = invalidate_slotwrapper(state[i], changeid, change.conditional) + invalidated = invalidate_slotwrapper(state[i], changeid) if invalidated !== nothing state[i] = invalidated end @@ -799,3 +823,9 @@ function stoverwrite1!(state::VarTable, change::StateUpdate) state[changeid] = newtype return state end + +function strefine1!(state::VarTable, refinement::StateRefinement) + (; newtyp, undef, slot) = refinement + state[slot] = VarState(newtyp, state[slot].ssadef, undef) + return state +end diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 91a44d3b117ab..1e1ae65327c4b 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -494,16 +494,16 @@ end # type-lattice for Conditional wrapper (NOTE never be merged with InterConditional) if isa(typea, Conditional) && isa(typeb, Const) if typeb.val === true - typeb = Conditional(typea.slot, Any, Union{}) + typeb = Conditional(typea.slot, typea.ssadef, Any, Union{}) elseif typeb.val === false - typeb = Conditional(typea.slot, Union{}, Any) + typeb = Conditional(typea.slot, typea.ssadef, Union{}, Any) end end if isa(typeb, Conditional) && isa(typea, Const) if typea.val === true - typea = Conditional(typeb.slot, Any, Union{}) + typea = Conditional(typeb.slot, typeb.ssadef, Any, Union{}) elseif typea.val === false - typea = Conditional(typeb.slot, Union{}, Any) + typea = Conditional(typeb.slot, typeb.ssadef, Union{}, Any) end end if isa(typea, Conditional) && isa(typeb, Conditional) @@ -511,7 +511,7 @@ end thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype) elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype) if thentype !== elsetype - return Conditional(typea.slot, thentype, elsetype) + return Conditional(typea.slot, typea.ssadef, thentype, elsetype) end end val = maybe_extract_const_bool(typea) diff --git a/base/compiler/types.jl b/base/compiler/types.jl index f315b7968fd9b..f95a7057093ea 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -47,14 +47,20 @@ MethodInfo(src::CodeInfo) = MethodInfo( A special wrapper that represents a local variable of a method being analyzed. This does not participate in the native type system nor the inference lattice, and it thus should be always unwrapped to `v.typ` when performing any type or lattice operations on it. + `v.undef` represents undefined-ness of this static parameter. If `true`, it means that the variable _may_ be undefined at runtime, otherwise it is guaranteed to be defined. If `v.typ === Bottom` it means that the variable is strictly undefined. + +`v.ssadef` represents the "reaching definition" for the variable. If negative, this refers +to a "virtual Ο•-block" preceding the given index. If a slot has the same `ssadef` at two +different points of execution, the slot contents are guaranteed to share identity (`xβ‚€ === x₁`). """ struct VarState typ + ssadef::Int undef::Bool - VarState(@nospecialize(typ), undef::Bool) = new(typ, undef) + VarState(@nospecialize(typ), ssadef::Int, undef::Bool) = new(typ, ssadef, undef) end struct AnalysisResults diff --git a/base/reflection.jl b/base/reflection.jl index 4b491ca9f6bd4..127671890ccd6 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -2246,7 +2246,7 @@ function print_statement_costs(io::IO, @nospecialize(tt::Type); else empty!(cst) resize!(cst, length(code.code)) - sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, false) for sp in match.sparams] + sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, #= ssadef =# typemin(Int), false) for sp in match.sparams] maxcost = Core.Compiler.statement_costs!(cst, code.code, code, sptypes, params) nd = ndigits(maxcost) irshow_config = IRShow.IRShowConfig() do io, linestart, idx diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index d95354cefa80c..371e93542beb2 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -410,10 +410,10 @@ CC.nsplit_impl(info::NoinlineCallInfo) = CC.nsplit(info.info) CC.getsplit_impl(info::NoinlineCallInfo, idx::Int) = CC.getsplit(info.info, idx) CC.getresult_impl(info::NoinlineCallInfo, idx::Int) = CC.getresult(info.info, idx) -function CC.abstract_call(interp::NoinlineInterpreter, - arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) +function CC.abstract_call(interp::NoinlineInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, + vtypes::Union{CC.VarTable,Nothing}, sv::CC.InferenceState, max_methods::Int) ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter, - arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) + arginfo::CC.ArgInfo, si::CC.StmtInfo, vtypes::Union{CC.VarTable,Nothing}, sv::CC.InferenceState, max_methods::Int) if sv.mod in noinline_modules(interp) return CC.CallMeta(ret.rt, ret.exct, ret.effects, NoinlineCallInfo(ret.info)) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 485ee579abd52..323554b9715ad 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1378,7 +1378,7 @@ let isa_tfunc(@nospecialize xs...) = @test isa_tfunc(typeof(Union{}), Union{}) === Union{} # any result is ok @test isa_tfunc(typeof(Union{}), Type{typeof(Union{})}) === Const(true) @test isa_tfunc(typeof(Union{}), Const(typeof(Union{}))) === Const(true) - let c = Conditional(0, Const(Union{}), Const(Union{})) + let c = Conditional(#= slot =# 0, #= ssadef =# 0, Const(Union{}), Const(Union{})) @test isa_tfunc(c, Const(Bool)) === Const(true) @test isa_tfunc(c, Type{Bool}) === Const(true) @test isa_tfunc(c, Const(Real)) === Const(true) @@ -1430,7 +1430,7 @@ let subtype_tfunc(@nospecialize xs...) = @test subtype_tfunc(Type{Union{}}, Any) === Const(true) # Union{} <: Any @test subtype_tfunc(Type{Union{}}, Union{Type{Int64}, Type{Float64}}) === Const(true) @test subtype_tfunc(Type{Union{}}, Union{Type{T}, Type{Float64}} where T) === Const(true) - let c = Conditional(0, Const(Union{}), Const(Union{})) + let c = Conditional(#= slot =# 0, #= ssadef =# 0, Const(Union{}), Const(Union{})) @test subtype_tfunc(c, Const(Bool)) === Const(true) # any result is ok end @test subtype_tfunc(Type{Val{1}}, Type{Val{T}} where T) === Bool @@ -1474,7 +1474,7 @@ let egal_tfunc @test egal_tfunc(Type{Union{Float32, Float64}}, Type{Union{Float32, Float64}}) === Bool @test egal_tfunc(typeof(Union{}), typeof(Union{})) === Bool # could be improved @test egal_tfunc(Const(typeof(Union{})), Const(typeof(Union{}))) === Const(true) - let c = Conditional(0, Const(Union{}), Const(Union{})) + let c = Conditional(#= slot =# 0, #= ssadef =# 0, Const(Union{}), Const(Union{})) @test egal_tfunc(c, Const(Bool)) === Const(false) @test egal_tfunc(c, Type{Bool}) === Const(false) @test egal_tfunc(c, Const(Real)) === Const(false) @@ -1485,17 +1485,17 @@ let egal_tfunc @test egal_tfunc(c, Bool) === Bool @test egal_tfunc(c, Any) === Bool end - let c = Conditional(0, Union{}, Const(Union{})) # === Const(false) - @test egal_tfunc(c, Const(false)) === Conditional(c.slot, c.elsetype, Union{}) - @test egal_tfunc(c, Const(true)) === Conditional(c.slot, Union{}, c.elsetype) + let c = Conditional(#= slot =# 0, #= ssadef =# 0, Union{}, Const(Union{})) # === Const(false) + @test egal_tfunc(c, Const(false)) === Conditional(c.slot, c.ssadef, c.elsetype, Union{}) + @test egal_tfunc(c, Const(true)) === Conditional(c.slot, c.ssadef, Union{}, c.elsetype) @test egal_tfunc(c, Const(nothing)) === Const(false) @test egal_tfunc(c, Int) === Const(false) @test egal_tfunc(c, Bool) === Bool @test egal_tfunc(c, Any) === Bool end - let c = Conditional(0, Const(Union{}), Union{}) # === Const(true) - @test egal_tfunc(c, Const(false)) === Conditional(c.slot, Union{}, c.thentype) - @test egal_tfunc(c, Const(true)) === Conditional(c.slot, c.thentype, Union{}) + let c = Conditional(#= slot =# 0, #= ssadef =# 0, Const(Union{}), Union{}) # === Const(true) + @test egal_tfunc(c, Const(false)) === Conditional(c.slot, c.ssadef, Union{}, c.thentype) + @test egal_tfunc(c, Const(true)) === Conditional(c.slot, c.ssadef, c.thentype, Union{}) @test egal_tfunc(c, Const(nothing)) === Const(false) @test egal_tfunc(c, Int) === Const(false) @test egal_tfunc(c, Bool) === Bool @@ -2289,20 +2289,20 @@ let 𝕃ᡒ = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance) isa_tfunc(@nospecialize xs...) = Core.Compiler.isa_tfunc(𝕃ᡒ, xs...) ifelse_tfunc(@nospecialize xs...) = Core.Compiler.ifelse_tfunc(𝕃ᡒ, xs...) - @test (MustAlias(2, AliasableField{Any}, 1, Int) βŠ‘ Int) - @test !(Int βŠ‘ MustAlias(2, AliasableField{Any}, 1, Int)) - @test (Int βŠ‘ MustAlias(2, AliasableField{Any}, 1, Any)) - @test (Const(42) βŠ‘ MustAlias(2, AliasableField{Any}, 1, Int)) - @test !(MustAlias(2, AliasableField{Any}, 1, Any) βŠ‘ Int) - @test tmerge(MustAlias(2, AliasableField{Any}, 1, Any), Const(nothing)) === Any - @test tmerge(MustAlias(2, AliasableField{Any}, 1, Int), Const(nothing)) === Union{Int,Nothing} - @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Any)) === Any - @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Int)) === Union{Int,Nothing} - @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Const(Bool)) === Const(true) - @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Type{Bool}) === Const(true) - @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Type{Bool}) === Const(false) - @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Int, Int) === Int - @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Int, Int) === Union{} + @test (MustAlias(2, 0, AliasableField{Any}, 1, Int) βŠ‘ Int) + @test !(Int βŠ‘ MustAlias(2, 0, AliasableField{Any}, 1, Int)) + @test (Int βŠ‘ MustAlias(2, 0, AliasableField{Any}, 1, Any)) + @test (Const(42) βŠ‘ MustAlias(2, 0, AliasableField{Any}, 1, Int)) + @test !(MustAlias(2, 0, AliasableField{Any}, 1, Any) βŠ‘ Int) + @test tmerge(MustAlias(2, 0, AliasableField{Any}, 1, Any), Const(nothing)) === Any + @test tmerge(MustAlias(2, 0, AliasableField{Any}, 1, Int), Const(nothing)) === Union{Int,Nothing} + @test tmerge(Const(nothing), MustAlias(2, 0, AliasableField{Any}, 1, Any)) === Any + @test tmerge(Const(nothing), MustAlias(2, 0, AliasableField{Any}, 1, Int)) === Union{Int,Nothing} + @test isa_tfunc(MustAlias(2, 0, AliasableField{Any}, 1, Bool), Const(Bool)) === Const(true) + @test isa_tfunc(MustAlias(2, 0, AliasableField{Any}, 1, Bool), Type{Bool}) === Const(true) + @test isa_tfunc(MustAlias(2, 0, AliasableField{Any}, 1, Int), Type{Bool}) === Const(false) + @test ifelse_tfunc(MustAlias(2, 0, AliasableField{Any}, 1, Bool), Int, Int) === Int + @test ifelse_tfunc(MustAlias(2, 0, AliasableField{Any}, 1, Int), Int, Int) === Union{} end maybeget_mustalias_tmerge(x::AliasableField) = x.f @@ -2461,7 +2461,7 @@ end |> only === Int # appropriate lattice order @test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x v = x.f # ::MustAlias(2, AliasableField{Any}, 1, Any) - if isa(v, Int) # ::Conditional(3, Int, Any) + if isa(v, Int) # ::Conditional(3, _, Int, Any) v = v # ::Int (∡ Int βŠ‘ MustAlias(2, AliasableField{Any}, 1, Any)) else v = 42 diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 281317ac25bf8..1acb3e613d5b1 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -1497,11 +1497,10 @@ let code = Any[ # Simulate the important results from inference interp = Core.Compiler.NativeInterpreter() sv = Core.Compiler.OptimizationState(mi, src, interp) - slot_id = 4 - for block_id = 3:5 - # (_4 !== nothing) conditional narrows the type, triggering PiNodes - sv.bb_vartables[block_id][slot_id] = VarState(Bool, #= maybe_undef =# false) - end + # (_4 !== nothing) conditional narrows the type, triggering PiNodes + sv.bb_vartables[#= block_id =# 3][#= slot_id =# 4] = VarState(Bool, #= def =# 5, #= maybe_undef =# false) + sv.bb_vartables[#= block_id =# 4][#= slot_id =# 4] = VarState(Bool, #= def =# 7, #= maybe_undef =# false) + sv.bb_vartables[#= block_id =# 5][#= slot_id =# 4] = VarState(Bool, #= def =# 7, #= maybe_undef =# false) ir = Core.Compiler.convert_to_ircode(src, sv) ir = Core.Compiler.slot2reg(ir, src, sv)