diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 3552525f70cf3..8626a6afebde8 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -30,12 +30,12 @@ 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 mutable struct SafeBox{T} @@ -109,7 +109,7 @@ end function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(func), 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(π•ƒβ‚š), strictneqpartialorder(π•ƒβ‚š), join(π•ƒβ‚š), join(𝕃ᡒ) argtypes = arginfo.argtypes @@ -274,7 +274,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun if sv isa InferenceState && fargs !== nothing state.slotrefinements = collect_slot_refinements(𝕃ᡒ, applicable, argtypes, fargs, sv) end - state.rettype = from_interprocedural!(interp, state.rettype, sv, arginfo, state.conditionals) + state.rettype = from_interprocedural!(interp, state.rettype, sv, arginfo, state.conditionals, vtypes) if call_result_unused(si) && !(state.rettype === Bottom) add_remark!(interp, sv, "Call result type was widened because the return value is unused") # We're mainly only here because the optimizer might want this code, @@ -418,15 +418,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" @@ -441,15 +441,17 @@ 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) if isa(arg, SlotNumber) + @assert vtypes !== nothing argtyp = widenslotwrapper(arginfo.argtypes[rt.slot]) βŠ‘ = partialorder(𝕃ᡒ) if rt.vartyp βŠ‘ argtyp - return MustAlias(arg, rt.vartyp, rt.fldidx, rt.fldtyp) + vtyp = vtypes[slot_id(arg)] + return MustAlias(arg, vtyp.ssadef, rt.vartyp, rt.fldidx, rt.fldtyp) else # TODO optimize this case? end @@ -459,7 +461,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) @@ -541,7 +543,8 @@ 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 + @assert vtypes !== nothing + return Conditional(slot, vtypes[slot].ssadef, thentype, elsetype) # record a Conditional improvement to this slot end return widenconditional(rt) end @@ -1404,7 +1407,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 @@ -1482,7 +1485,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) @@ -1568,12 +1571,13 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) end return Future(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 @@ -1582,7 +1586,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n @assert !isvarargtype(itertype) iterateresult = Future{AbstractIterationResult}() - call1future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true, false), sv)::Future + call1future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true, false), vtypes, sv)::Future function inferiterate(interp, sv) call1 = call1future[] stateordonet = call1.rt @@ -1638,7 +1642,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n valtype = getfield_tfunc(𝕃ᡒ, stateordonet, Const(1)) push!(ret, valtype) statetype = nstatetype - call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), sv)::Future + call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), vtypes, sv)::Future if !isready(call2future) nextstate = 0x1 return false @@ -1680,7 +1684,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), sv)::Future + call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), vtypes, sv)::Future if !isready(call2future) nextstate = 0x2 return false @@ -1711,7 +1715,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 = Core.Box(argtype_by_index(argtypes, 2)) aft = argtype_by_index(argtypes, 3) (itft.contents === Bottom || aft === Bottom) && return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())) @@ -1770,7 +1775,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: ti = argtypesi[j] j += 1 if !isvarargtype(ti) - ctfuture = precise_container_type(interp, itft.contents, ti, sv)::Future + ctfuture = precise_container_type(interp, itft.contents, ti, vtypes, sv)::Future if !isready(ctfuture) nextstate = 0x1 return false @@ -1778,7 +1783,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: end (;cti, info, ai_effects) = ctfuture[] else - ctfuture = precise_container_type(interp, itft.contents, unwrapva(ti), sv)::Future + ctfuture = precise_container_type(interp, itft.contents, unwrapva(ti), vtypes, sv)::Future if !isready(ctfuture) nextstate = 0x2 return false @@ -1849,7 +1854,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: break end end - callfuture = abstract_call(interp, ArgInfo(nothing, ct), si, sv, max_methods)::Future + callfuture = abstract_call(interp, ArgInfo(nothing, ct), si, vtypes, sv, max_methods)::Future if !isready(callfuture) nextstate = 0x3 return false @@ -1951,7 +1956,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) @@ -1992,7 +1997,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 @@ -2007,7 +2014,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) @@ -2025,7 +2034,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 === (===) @@ -2036,16 +2047,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) @@ -2076,18 +2091,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.Intrinsics.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) @@ -2110,7 +2131,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 @@ -2120,7 +2143,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 @@ -2215,7 +2240,7 @@ function get_ci_abi(ci::CodeInstance) (def::MethodInstance).specTypes 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β€²) @@ -2323,7 +2348,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt update_valid_age!(sv, world_range(const_edge)) end end - rt = from_interprocedural!(interp, rt, sv, arginfoβ€², sig) + rt = from_interprocedural!(interp, rt, sv, arginfoβ€², sig, vtypes) info = InvokeCallInfo(edge, match, const_result, lookupsig_box.contents) if !match.fully_covers effects = Effects(effects; nothrow=false) @@ -2340,10 +2365,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, false), sv, #=max_methods=#1)::Future + call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false, false), vtypes, sv, #=max_methods=#1)::Future return Future{CallMeta}(call, interp, sv) do call, interp, sv return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end @@ -2636,8 +2661,8 @@ 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 argtypes::Vector{Any} = arginfo.argtypes # declare type because the closure below captures `argtypes` fargs = arginfo.fargs @@ -2645,14 +2670,14 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), 𝕃ᡒ = 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 @@ -2680,7 +2705,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif f === Core.get_binding_type return Future(abstract_eval_get_binding_type(interp, sv, argtypes)) 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 @@ -2726,7 +2751,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), resize!(T, la) atype = Tuple{T...} T[1] = Const(TypeVar) - let call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, T), si, atype, sv, max_methods)::Future + let call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, T), si, atype, vtypes, sv, max_methods)::Future return Future{CallMeta}(call, interp, sv) do call, interp, sv n = argtypes[2] ub_var = Const(Any) @@ -2750,7 +2775,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), end end elseif f === UnionAll - let call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods)::Future + let 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)::Future return Future{CallMeta}(call, interp, sv) do call, interp, sv return abstract_call_unionall(interp, argtypes, call) end @@ -2765,12 +2790,12 @@ 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 === - let callfuture = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods)::Future, - rtfuture = abstract_call_known(interp, (===), arginfo, si, sv, max_methods)::Future + let callfuture = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, vtypes, sv, max_methods)::Future, + rtfuture = abstract_call_known(interp, (===), arginfo, si, vtypes, sv, max_methods)::Future return Future{CallMeta}(isready(callfuture) && isready(rtfuture), interp, sv) do interp, sv local rty = rtfuture[].rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else + 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 @@ -2786,18 +2811,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 Future(CallMeta(typename_static(argtypes[2]), Bottom, EFFECTS_TOTAL, MethodResultPure())) elseif f === Core._hasmethod return Future(_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)::Future + return abstract_call_gf_by_type(interp, f, arginfo, si, atype, vtypes, sv, max_methods)::Future 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) tt = closure.typ ocargsig = rewrap_unionall((unwrap_unionall(tt)::DataType).parameters[1], tt) @@ -2848,7 +2873,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, exct = exct βŠ” TypeError 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(edge, match, const_result) return CallMeta(rt, exct, effects, info) end @@ -2865,13 +2890,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) @@ -2887,20 +2912,22 @@ function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), # 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) atype === Bottom && return Future(CallMeta(Union{}, Union{}, EFFECTS_THROWS, NoCallInfo())) # accidentally unreachable - return abstract_call_gf_by_type(interp, nothing, arginfo, si, atype, sv, max_methods)::Future + return abstract_call_gf_by_type(interp, nothing, arginfo, si, atype, vtypes, sv, max_methods)::Future 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) @@ -2959,7 +2986,7 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, sstate::S # 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, false), sv)::Future + abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false, false), sstate.vtypes, sv)::Future rt = e.args[1] isconcretetype(rt) || (rt = Any) return RTEffects(rt, Any, EFFECTS_UNKNOWN) @@ -3053,7 +3080,7 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sstate::St add_curr_ssaflag!(sv, IR_FLAG_UNUSED) end si = StmtInfo(!unused, sstate.saw_latestworld) - call = abstract_call(interp, arginfo, si, sv)::Future + call = abstract_call(interp, arginfo, si, sstate.vtypes, sv)::Future Future{Any}(call, interp, sv) do call, interp, sv # this only is needed for the side-effect, sequenced before any task tries to consume the return value, # which this will do even without returning this Future @@ -3233,7 +3260,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, false), sv, #=check=#false)::Future + ArgInfo(nothing, argtypes), StmtInfo(true, false), sstate.vtypes, sv, #=check=#false)::Future Future{Any}(callinfo, interp, sv) do callinfo, interp, sv sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) nothing @@ -3265,7 +3292,7 @@ function abstract_eval_isdefined_expr(::AbstractInterpreter, e::Expr, sstate::St elseif !vtyp.undef rt = Const(true) # definitely assigned previously else # form `Conditional` to refine `vtyp.undef` in the then branch - rt = Conditional(sym, widenslotwrapper(vtyp.typ), widenslotwrapper(vtyp.typ); isdefined=true) + rt = Conditional(sym, vtyp.ssadef, widenslotwrapper(vtyp.typ), widenslotwrapper(vtyp.typ); isdefined=true) end return RTEffects(rt, Union{}, EFFECTS_TOTAL) end @@ -3821,7 +3848,7 @@ end @goto injectresult end if isa(stmt, NewvarNode) - changes = StateUpdate(stmt.slot, VarState(Bottom, true)) + changes = StateUpdate(stmt.slot, VarState(Bottom, frame.currpc, #= undef =# true)) elseif isa(stmt, PhiNode) add_curr_ssaflag!(frame, IR_FLAGS_REMOVABLE) # Implement convergence for PhiNodes. In particular, PhiNodes need to tmerge over @@ -3844,7 +3871,7 @@ end if 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 elseif (hd === :code_coverage_effect || # :boundscheck can be narrowed to Bool @@ -3890,7 +3917,7 @@ end end end if lhs !== nothing && rt !== Bottom - changes = StateUpdate(lhs::SlotNumber, VarState(rt, false)) + changes = StateUpdate(lhs::SlotNumber, VarState(rt, frame.currpc, #= undef =# false)) end end return AbstractEvalBasicStatementResult(rt, exct, effects, changes, refinements, currsaw_latestworld) @@ -3921,7 +3948,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) @@ -3956,13 +3983,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 @@ -4075,14 +4102,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 @@ -4246,9 +4276,10 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr 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) @@ -4293,27 +4324,27 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr # 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, currsaw_latestworld) - 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, currsaw_latestworld) @@ -4442,15 +4473,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` @@ -4466,18 +4506,23 @@ function conditional_change(𝕃ᡒ::AbstractLattice, currstate::VarTable, condt # "causes" since we ignored those in the comparison newtyp = tmerge(𝕃ᡒ, newtyp, LimitedAccuracy(Bottom, oldtyp.causes)) end - # if this `Conditional` is 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) + # if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` information + 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)) + if then_or_else === :then + thentype = condt.thentype + elsetype = Union{} + elseif then_or_else === :else + thentype = Union{} + elsetype = 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/Compiler/src/inferencestate.jl b/Compiler/src/inferencestate.jl index 8a8e5354e3d59..6494280098868 100644 --- a/Compiler/src/inferencestate.jl +++ b/Compiler/src/inferencestate.jl @@ -355,10 +355,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 ] ssaflags = copy(src.ssaflags) @@ -764,7 +764,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/Compiler/src/optimize.jl b/Compiler/src/optimize.jl index 27fb1e2168639..396eb0e351f7b 100644 --- a/Compiler/src/optimize.jl +++ b/Compiler/src/optimize.jl @@ -249,7 +249,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/Compiler/src/reflection_interface.jl b/Compiler/src/reflection_interface.jl index 3fc182685e598..6cd21355918c2 100644 --- a/Compiler/src/reflection_interface.jl +++ b/Compiler/src/reflection_interface.jl @@ -42,7 +42,7 @@ end function statement_costs!(interp::AbstractInterpreter, cost::Vector{Int}, body::Vector{Any}, src::Union{CodeInfo, IRCode}, match::Core.MethodMatch) params = OptimizationParams(interp) - sptypes = VarState[VarState(sp, false) for sp in match.sparams] + sptypes = VarState[VarState(sp, #= ssadef =# typemin(Int), false) for sp in match.sparams] return statement_costs!(cost, body, src, sptypes, params) end diff --git a/Compiler/src/ssair/irinterp.jl b/Compiler/src/ssair/irinterp.jl index 3d72da72625be..7cb64c616885d 100644 --- a/Compiler/src/ssair/irinterp.jl +++ b/Compiler/src/ssair/irinterp.jl @@ -55,7 +55,7 @@ end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sstate::StatementState, irsv::IRInterpretationState) si = StmtInfo(true, sstate.saw_latestworld) # TODO better job here? - call = abstract_call(interp, arginfo, si, irsv)::Future + call = abstract_call(interp, arginfo, si, sstate.vtypes, irsv)::Future Future{Any}(call, interp, irsv) do call, interp, irsv irsv.ir.stmts[irsv.curridx][:info] = call.info nothing diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 71719d75144b3..a880550c05657 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -233,7 +233,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 @@ -354,14 +354,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 @@ -1363,7 +1363,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 @@ -1424,7 +1424,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, si.saw_latestworld), sv, #=max_methods=#1) + callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true, si.saw_latestworld), vtypes, sv, #=max_methods=#1) TF = Core.Box(TF) RT = Core.Box(RT) return Future{CallMeta}(callinfo, interp, sv) do callinfo, interp, sv @@ -3113,7 +3113,8 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s old_restrict = sv.restrict_abstract_call_sites sv.restrict_abstract_call_sites = false end - 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) tt = Core.Box(tt) return Future{CallMeta}(call, interp, sv) do call, interp, sv if isa(sv, InferenceState) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 2d54ad696b6bf..d75f57e0aabec 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -883,7 +883,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/Compiler/src/typelattice.jl b/Compiler/src/typelattice.jl index f4c3b051d3e3f..c31838fbde380 100644 --- a/Compiler/src/typelattice.jl +++ b/Compiler/src/typelattice.jl @@ -32,7 +32,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 @@ -43,27 +43,30 @@ 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) limited = may_form_limited_typ(thentype, elsetype, Bool) limited !== nothing && return limited - 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) 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 @@ -90,21 +93,22 @@ 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" limited = may_form_limited_typ(vartyp, fldtyp, fldtyp) limited !== nothing && return limited - 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 @@ -130,8 +134,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 @@ -145,8 +151,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 """ @@ -284,6 +302,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) @@ -332,7 +351,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) @@ -343,7 +362,7 @@ end elsefields === nothing || (elsefields[fldidx] = elsetype) undefs[fldidx] = false end - return Conditional(slot, + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, undefs, thenfields), elsefields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, undefs, elsefields)) else @@ -360,7 +379,7 @@ end elsefields === nothing || push!(elsefields, t) end end - return Conditional(slot, + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp_widened, thenfields), elsefields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp_widened, elsefields)) end @@ -713,34 +732,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 @@ -757,7 +781,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 @@ -768,6 +792,12 @@ function stoverwrite1!(state::VarTable, change::StateUpdate) return state end +function strefine1!(state::VarTable, refinement::StateRefinement) + (; newtyp, undef, slot) = refinement + state[slot] = VarState(newtyp, state[slot].ssadef, undef) + return state +end + # The ::AbstractLattice argument is unused and simply serves to disambiguate # different instances of the compiler that may share the `Core.PartialStruct` # type. diff --git a/Compiler/src/typelimits.jl b/Compiler/src/typelimits.jl index af5e7964be0a6..d367bdb94495f 100644 --- a/Compiler/src/typelimits.jl +++ b/Compiler/src/typelimits.jl @@ -500,16 +500,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) @@ -517,7 +517,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/Compiler/src/types.jl b/Compiler/src/types.jl index 7bbd59b742a93..8d8a21c3c6f8a 100644 --- a/Compiler/src/types.jl +++ b/Compiler/src/types.jl @@ -65,14 +65,23 @@ SpecInfo(src::CodeInfo) = SpecInfo( 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 zero, then the value comes from an argument. +If negative, this refers to a "virtual Ο•-block" preceding the given index, +that would have been inserted as the value of this slot in a truly SSA-form IR. +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/Compiler/test/AbstractInterpreter.jl b/Compiler/test/AbstractInterpreter.jl index 12da527225a7e..59681c8952cf0 100644 --- a/Compiler/test/AbstractInterpreter.jl +++ b/Compiler/test/AbstractInterpreter.jl @@ -408,10 +408,10 @@ Compiler.nsplit_impl(info::NoinlineCallInfo) = Compiler.nsplit(info.info) Compiler.getsplit_impl(info::NoinlineCallInfo, idx::Int) = Compiler.getsplit(info.info, idx) Compiler.getresult_impl(info::NoinlineCallInfo, idx::Int) = Compiler.getresult(info.info, idx) -function Compiler.abstract_call(interp::NoinlineInterpreter, - arginfo::Compiler.ArgInfo, si::Compiler.StmtInfo, sv::Compiler.InferenceState, max_methods::Int) +function Compiler.abstract_call(interp::NoinlineInterpreter, arginfo::Compiler.ArgInfo, si::Compiler.StmtInfo, + vtypes::Union{Compiler.VarTable,Nothing}, sv::Compiler.InferenceState, max_methods::Int) ret = @invoke Compiler.abstract_call(interp::Compiler.AbstractInterpreter, - arginfo::Compiler.ArgInfo, si::Compiler.StmtInfo, sv::Compiler.InferenceState, max_methods::Int) + arginfo::Compiler.ArgInfo, si::Compiler.StmtInfo, vtypes::Union{Compiler.VarTable,Nothing}, sv::Compiler.InferenceState, max_methods::Int) return Compiler.Future{Compiler.CallMeta}(ret, interp, sv) do ret, interp, sv if sv.mod in noinline_modules(interp) (;rt, exct, effects, info) = ret diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index 58cd3db49c371..7e335c0ddb4a9 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -1381,7 +1381,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) @@ -1433,7 +1433,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 @@ -1477,7 +1477,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) @@ -1488,17 +1488,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 @@ -2299,21 +2299,21 @@ let 𝕃ᡒ = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance) isa_tfunc(@nospecialize xs...) = Compiler.isa_tfunc(𝕃ᡒ, xs...) ifelse_tfunc(@nospecialize xs...) = 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 (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} tmerge(Const(AbstractVector{<:Any}), Const(AbstractVector{T} where {T})) # issue #56913 - @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 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 @@ -2472,7 +2472,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 @@ -6515,4 +6515,16 @@ end <: Bool Core.get_binding_type(m, n, xs...) end <: Type +# JuliaLang/julia#55548: invalidate stale slot wrapper types in `ssavaluetypes` +_issue55548_proj1(a, b) = a +function issue55548(a) + a = Base.inferencebarrier(a)::Union{Int64,Float64} + if _issue55548_proj1(isa(a, Int64), (a = Base.inferencebarrier(1.0)::Union{Int64,Float64}; true)) + return a + end + return 2 +end +@test Float64 <: Base.infer_return_type(issue55548, (Int,)) +@test issue55548(Int64(0)) === 1.0 + end # module inference diff --git a/Compiler/test/irpasses.jl b/Compiler/test/irpasses.jl index 758efaab9ab6b..42d8b788cc617 100644 --- a/Compiler/test/irpasses.jl +++ b/Compiler/test/irpasses.jl @@ -1499,11 +1499,10 @@ let code = Any[ # Simulate the important results from inference interp = Compiler.NativeInterpreter() sv = 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 = Compiler.convert_to_ircode(src, sv) ir = Compiler.slot2reg(ir, src, sv)