From 207834503697681b56b00292302fbcd0ece6bab1 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 27 Aug 2024 11:02:09 -0400 Subject: [PATCH 1/2] inference: track reaching defs for slots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change effectively computes the SSA / Ο•-nodes for program slots as part of type-inference, using the "path-convergence criterion" for SSA. This allows us to conveniently reason about slot identity (in typical SSA fashion) without having to quadratically expand all of our SSA type state over the CFG. --- base/compiler/abstractinterpretation.jl | 221 ++++++++++++++---------- base/compiler/inferencestate.jl | 8 +- base/compiler/optimize.jl | 2 +- base/compiler/ssair/irinterp.jl | 4 +- base/compiler/tfuncs.jl | 15 +- base/compiler/typeinfer.jl | 2 +- base/compiler/typelattice.jl | 73 +++++--- base/compiler/typelimits.jl | 10 +- base/compiler/types.jl | 8 +- base/reflection.jl | 2 +- test/compiler/AbstractInterpreter.jl | 6 +- test/compiler/inference.jl | 20 +-- test/compiler/irpasses.jl | 9 +- 13 files changed, 228 insertions(+), 152 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f3fc4e0423173..30e9faae14161 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,7 +389,7 @@ 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) @@ -397,7 +397,7 @@ function from_interprocedural!(interp::AbstractInterpreter, @nospecialize(rt), s 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" @@ -430,7 +430,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 +512,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 +1430,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 +1508,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 +1594,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 +1642,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 +1676,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 +1689,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 +1715,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 +1774,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 +1875,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) @@ -1929,7 +1931,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 +1951,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 +1964,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 +2008,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 +2048,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 +2060,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 +2147,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 +2198,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 +2211,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 +2240,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 +2300,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 +2312,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 +2324,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 +2342,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 +2380,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 +2397,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 +2418,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 +2491,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 +2574,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 +2593,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 +2724,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 +2757,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 @@ -3080,13 +3103,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 +3230,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 +3245,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 +3255,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 +3276,18 @@ 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) + # TODO: This should probably be `0`, not typemin(Int) + vartable[i] = VarState(Bottom, #= ssadef =# typemin(Int), i > nargtypes) end return vartable end @@ -3390,9 +3417,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 +3465,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 +3606,25 @@ 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) + # TODO: Remove typemin condition... + # @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 +3641,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..c2d2d964cfb7d 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -315,11 +315,13 @@ mutable struct InferenceState nargtypes = length(argtypes) for i = 1:nslots argtyp = (i > nargtypes) ? Bottom : argtypes[i] + # 0 = function entry (think carefully) 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) + # 0 = function entry (think carefully) + bb_vartable1[i] = VarState(argtyp, #= ssadef =# 0, i > nargtypes) end src.ssavaluetypes = ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] @@ -712,7 +714,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..a9fe013e632b9 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 @@ -184,8 +187,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 +343,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) @@ -387,7 +403,7 @@ end elsefields === nothing || (elsefields[i] = elsetype) end end - return Conditional(slot, + return Conditional(slot, typemin(Int), # TODO thenfields === nothing ? Bottom : PartialStruct(vartyp.typ, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp.typ, elsefields)) else @@ -404,7 +420,7 @@ end elsefields === nothing || push!(elsefields, t) end end - return Conditional(slot, + return Conditional(slot, typemin(Int), thenfields === nothing ? Bottom : PartialStruct(vartyp_widened, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp_widened, elsefields)) end @@ -745,34 +761,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 +810,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 +820,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..1f111d5731fd9 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{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{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 7cb97db7b9cf4..f632efb2910bb 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 @@ -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) From e60a5ee1b843d27ddc35bcb0d295cb55b0798dba Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 28 Aug 2024 11:54:54 -0400 Subject: [PATCH 2/2] Make `MustAlias` propagate ssadef to `Conditional` Another change will probably be needed to make sure that `MustAlias` itself is invalidated by `.defssa`, but this is enough to make sure that any Conditional derived from MustAlias works correctly. --- base/compiler/abstractinterpretation.jl | 18 +++++++++------- base/compiler/inferencestate.jl | 2 -- base/compiler/typelattice.jl | 21 +++++++++++-------- test/compiler/AbstractInterpreter.jl | 4 ++-- test/compiler/inference.jl | 28 ++++++++++++------------- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 30e9faae14161..c307adbb93a4f 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -392,7 +392,7 @@ function from_interprocedural!(interp::AbstractInterpreter, @nospecialize(rt), s 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) @@ -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 @@ -1916,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 @@ -3068,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) @@ -3286,7 +3290,6 @@ end function init_vartable!(vartable::VarTable, frame::InferenceState) nargtypes = length(frame.result.argtypes) for i = 1:length(vartable) - # TODO: This should probably be `0`, not typemin(Int) vartable[i] = VarState(Bottom, #= ssadef =# typemin(Int), i > nargtypes) end return vartable @@ -3612,8 +3615,7 @@ function apply_refinement!(𝕃ᡒ::AbstractLattice, slot::SlotNumber, @nospecia end function conditional_valid(condt::Conditional, currstate::VarTable) - # TODO: Remove typemin condition... - # @assert condt.ssadef != typemin(Int) + @assert condt.ssadef != typemin(Int) return currstate[condt.slot].ssadef == condt.ssadef end diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index c2d2d964cfb7d..50633a86aaddb 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -315,12 +315,10 @@ mutable struct InferenceState nargtypes = length(argtypes) for i = 1:nslots argtyp = (i > nargtypes) ? Bottom : argtypes[i] - # 0 = function entry (think carefully) if argtyp === Bool && has_conditional(typeinf_lattice(interp)) argtyp = Conditional(i, #= ssadef =# 0, Const(true), Const(false)) end slottypes[i] = argtyp - # 0 = function entry (think carefully) bb_vartable1[i] = VarState(argtyp, #= ssadef =# 0, i > nargtypes) end src.ssavaluetypes = ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index a9fe013e632b9..0152d87426685 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -136,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 @@ -172,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 @@ -392,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) @@ -403,7 +406,7 @@ end elsefields === nothing || (elsefields[i] = elsetype) end end - return Conditional(slot, typemin(Int), # TODO + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(vartyp.typ, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp.typ, elsefields)) else @@ -420,7 +423,7 @@ end elsefields === nothing || push!(elsefields, t) end end - return Conditional(slot, typemin(Int), + return Conditional(slot, ssadef, thenfields === nothing ? Bottom : PartialStruct(vartyp_widened, thenfields), elsefields === nothing ? Bottom : PartialStruct(vartyp_widened, elsefields)) end diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 1f111d5731fd9..371e93542beb2 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -411,9 +411,9 @@ 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, - vtypes::Union{VarTable,Nothing}, sv::CC.InferenceState, max_methods::Int) + 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, vtypes::Union{VarTable,Nothing}, 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 f632efb2910bb..72b6a607620ba 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -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