# TODO add more documentations function widenlattice end function is_valid_lattice_norec end """ struct JLTypeLattice <: AbstractLattice A singleton type representing the lattice of Julia types, without any inference extensions. """ struct JLTypeLattice <: AbstractLattice; end widenlattice(::JLTypeLattice) = error("Type lattice is the least-precise lattice available") is_valid_lattice_norec(::JLTypeLattice, @nospecialize(elem)) = isa(elem, Type) """ struct ConstsLattice <: AbstractLattice A lattice extending `JLTypeLattice` and adjoining `Const` and `PartialTypeVar`. """ struct ConstsLattice <: AbstractLattice; end widenlattice(::ConstsLattice) = JLTypeLattice() is_valid_lattice_norec(::ConstsLattice, @nospecialize(elem)) = isa(elem, Const) || isa(elem, PartialTypeVar) """ struct PartialsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice A lattice extending a base lattice `๐•ƒ` and adjoining `PartialStruct` and `PartialOpaque`. """ struct PartialsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::PartialsLattice) = ๐•ƒ.parent is_valid_lattice_norec(::PartialsLattice, @nospecialize(elem)) = isa(elem, PartialStruct) || isa(elem, PartialOpaque) """ struct ConditionalsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice A lattice extending a base lattice `๐•ƒ` and adjoining `Conditional`. """ struct ConditionalsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::ConditionalsLattice) = ๐•ƒ.parent is_valid_lattice_norec(::ConditionalsLattice, @nospecialize(elem)) = isa(elem, Conditional) """ struct InterConditionalsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice A lattice extending a base lattice `๐•ƒ` and adjoining `InterConditional`. """ struct InterConditionalsLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::InterConditionalsLattice) = ๐•ƒ.parent is_valid_lattice_norec(::InterConditionalsLattice, @nospecialize(elem)) = isa(elem, InterConditional) """ struct MustAliasesLattice{๐•ƒ<:AbstractLattice} A lattice extending lattice `๐•ƒ` and adjoining `MustAlias`. """ struct MustAliasesLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::MustAliasesLattice) = ๐•ƒ.parent is_valid_lattice_norec(::MustAliasesLattice, @nospecialize(elem)) = isa(elem, MustAlias) """ struct InterMustAliasesLattice{๐•ƒ<:AbstractLattice} A lattice extending lattice `๐•ƒ` and adjoining `InterMustAlias`. """ struct InterMustAliasesLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::InterMustAliasesLattice) = ๐•ƒ.parent is_valid_lattice_norec(::InterMustAliasesLattice, @nospecialize(elem)) = isa(elem, InterMustAlias) const AnyConditionalsLattice{๐•ƒ<:AbstractLattice} = Union{ConditionalsLattice{๐•ƒ}, InterConditionalsLattice{๐•ƒ}} const AnyMustAliasesLattice{๐•ƒ<:AbstractLattice} = Union{MustAliasesLattice{๐•ƒ}, InterMustAliasesLattice{๐•ƒ}} const SimpleInferenceLattice = typeof(PartialsLattice(ConstsLattice())) const BaseInferenceLattice = typeof(ConditionalsLattice(SimpleInferenceLattice.instance)) const IPOResultLattice = typeof(InterConditionalsLattice(SimpleInferenceLattice.instance)) """ struct InferenceLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice The full lattice used for abstract interpretation during inference. Extends a base lattice `๐•ƒ` and adjoins `LimitedAccuracy`. """ struct InferenceLattice{๐•ƒ<:AbstractLattice} <: AbstractLattice parent::๐•ƒ end widenlattice(๐•ƒ::InferenceLattice) = ๐•ƒ.parent is_valid_lattice_norec(::InferenceLattice, @nospecialize(elem)) = isa(elem, LimitedAccuracy) """ tmeet(๐•ƒ::AbstractLattice, a, b::Type) Compute the lattice meet of lattice elements `a` and `b` over the lattice `๐•ƒ`. If `๐•ƒ` is `JLTypeLattice`, this is equivalent to type intersection. Note that currently `b` is restricted to being a type (interpreted as a lattice element in the `JLTypeLattice` sub-lattice of `๐•ƒ`). """ function tmeet end function tmeet(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) ti = typeintersect(a, b) valid_as_lattice(ti) || return Bottom return ti end """ tmerge(๐•ƒ::AbstractLattice, a, b) Compute a lattice join of elements `a` and `b` over the lattice `๐•ƒ`. Note that the computed element need not be the least upper bound of `a` and `b`, but rather, we impose additional limitations on the complexity of the joined element, ideally without losing too much precision in common cases and remaining mostly associative and commutative. """ function tmerge end """ tmerge_field(๐•ƒ::AbstractLattice, a, b) -> nothing or lattice element Compute a lattice join of elements `a` and `b` over the lattice `๐•ƒ`, where `a` and `b` are fields of `PartialStruct` or `Const`. This is an opt-in interface to allow external lattice implementation to provide its own field-merge strategy. If it returns `nothing`, `tmerge(::PartialsLattice, ...)` will use the default aggressive type merge implementation that does not use `tmerge` recursively to reach convergence. """ function tmerge_field end function tmerge_field(๐•ƒ::AbstractLattice, @nospecialize(a), @nospecialize(b)) return tmerge_field(widenlattice(๐•ƒ), a, b) end tmerge_field(::JLTypeLattice, @nospecialize(a), @nospecialize(b)) = nothing """ โŠ‘(๐•ƒ::AbstractLattice, a, b) Compute the lattice ordering (i.e. less-than-or-equal) relationship between lattice elements `a` and `b` over the lattice `๐•ƒ`. If `๐•ƒ` is `JLTypeLattice`, this is equivalent to subtyping. """ function โŠ‘ end @nospecializeinfer โŠ‘(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) = a <: b """ โŠ(๐•ƒ::AbstractLattice, a, b) -> Bool The strict partial order over the type inference lattice. This is defined as the irreflexive kernel of `โŠ‘`. """ @nospecializeinfer โŠ(๐•ƒ::AbstractLattice, @nospecialize(a), @nospecialize(b)) = โŠ‘(๐•ƒ, a, b) && !โŠ‘(๐•ƒ, b, a) """ โ‹ค(๐•ƒ::AbstractLattice, a, b) -> Bool This order could be used as a slightly more efficient version of the strict order `โŠ`, where we can safely assume `a โŠ‘ b` holds. """ @nospecializeinfer โ‹ค(๐•ƒ::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !โŠ‘(๐•ƒ, b, a) """ is_lattice_equal(๐•ƒ::AbstractLattice, a, b) -> Bool Check if two lattice elements are partial order equivalent. This is basically `a โŠ‘ b && b โŠ‘ a` in the lattice of `๐•ƒ` but (optionally) with extra performance optimizations. """ @nospecializeinfer function is_lattice_equal(๐•ƒ::AbstractLattice, @nospecialize(a), @nospecialize(b)) a === b && return true return โŠ‘(๐•ƒ, a, b) && โŠ‘(๐•ƒ, b, a) end """ has_nontrivial_extended_info(๐•ƒ::AbstractLattice, t) -> Bool Determines whether the given lattice element `t` of `๐•ƒ` has non-trivial extended lattice information that would not be available from the type itself. """ @nospecializeinfer has_nontrivial_extended_info(๐•ƒ::AbstractLattice, @nospecialize t) = has_nontrivial_extended_info(widenlattice(๐•ƒ), t) @nospecializeinfer function has_nontrivial_extended_info(๐•ƒ::PartialsLattice, @nospecialize t) isa(t, PartialStruct) && return true isa(t, PartialOpaque) && return true return has_nontrivial_extended_info(widenlattice(๐•ƒ), t) end @nospecializeinfer function has_nontrivial_extended_info(๐•ƒ::ConstsLattice, @nospecialize t) isa(t, PartialTypeVar) && return true if isa(t, Const) val = t.val return !issingletontype(typeof(val)) && !(isa(val, Type) && hasuniquerep(val)) end return has_nontrivial_extended_info(widenlattice(๐•ƒ), t) end @nospecializeinfer has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false """ is_const_prop_profitable_arg(๐•ƒ::AbstractLattice, t) -> Bool Determines whether the given lattice element `t` of `๐•ƒ` has new extended lattice information that should be forwarded along with constant propagation. """ @nospecializeinfer is_const_prop_profitable_arg(๐•ƒ::AbstractLattice, @nospecialize t) = is_const_prop_profitable_arg(widenlattice(๐•ƒ), t) @nospecializeinfer function is_const_prop_profitable_arg(๐•ƒ::PartialsLattice, @nospecialize t) if isa(t, PartialStruct) return true # might be a bit aggressive, may want to enable some check like follows: # for i = 1:length(t.fields) # fld = t.fields[i] # isconstType(fld) && return true # is_const_prop_profitable_arg(fld) && return true # fld โŠ fieldtype(t.typ, i) && return true # end # return false end isa(t, PartialOpaque) && return true return is_const_prop_profitable_arg(widenlattice(๐•ƒ), t) end @nospecializeinfer function is_const_prop_profitable_arg(๐•ƒ::ConstsLattice, @nospecialize t) if isa(t, Const) # don't consider mutable values useful constants val = t.val return isa(val, Symbol) || isa(val, Type) || !ismutable(val) end isa(t, PartialTypeVar) && return false # this isn't forwardable return is_const_prop_profitable_arg(widenlattice(๐•ƒ), t) end @nospecializeinfer is_const_prop_profitable_arg(::JLTypeLattice, @nospecialize t) = false @nospecializeinfer is_forwardable_argtype(๐•ƒ::AbstractLattice, @nospecialize(x)) = is_forwardable_argtype(widenlattice(๐•ƒ), x) @nospecializeinfer function is_forwardable_argtype(๐•ƒ::ConditionalsLattice, @nospecialize x) isa(x, Conditional) && return true return is_forwardable_argtype(widenlattice(๐•ƒ), x) end @nospecializeinfer function is_forwardable_argtype(๐•ƒ::PartialsLattice, @nospecialize x) isa(x, PartialStruct) && return true isa(x, PartialOpaque) && return true return is_forwardable_argtype(widenlattice(๐•ƒ), x) end @nospecializeinfer function is_forwardable_argtype(๐•ƒ::ConstsLattice, @nospecialize x) isa(x, Const) && return true return is_forwardable_argtype(widenlattice(๐•ƒ), x) end @nospecializeinfer function is_forwardable_argtype(::JLTypeLattice, @nospecialize x) return false end """ widenreturn(๐•ƒแตข::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess widenreturn_noslotwrapper(๐•ƒแตข::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess Appropriately converts inferred type of a return value `rt` to such a type that we know we can store in the cache and is valid and good inter-procedurally, E.g. if `rt isa Conditional` then `rt` should be converted to `InterConditional` or the other cachable lattice element. External lattice `๐•ƒแตข::ExternalLattice` may overload: - `widenreturn(๐•ƒแตข::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` - `widenreturn_noslotwrapper(๐•ƒแตข::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` """ function widenreturn end, function widenreturn_noslotwrapper end @nospecializeinfer is_valid_lattice(๐•ƒ::AbstractLattice, @nospecialize(elem)) = is_valid_lattice_norec(๐•ƒ, elem) && is_valid_lattice(widenlattice(๐•ƒ), elem) @nospecializeinfer is_valid_lattice(๐•ƒ::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(๐•ƒ, elem) has_conditional(๐•ƒ::AbstractLattice) = has_conditional(widenlattice(๐•ƒ)) has_conditional(::AnyConditionalsLattice) = true has_conditional(::JLTypeLattice) = false has_mustalias(๐•ƒ::AbstractLattice) = has_mustalias(widenlattice(๐•ƒ)) has_mustalias(::AnyMustAliasesLattice) = true has_mustalias(::JLTypeLattice) = false has_extended_unionsplit(๐•ƒ::AbstractLattice) = has_extended_unionsplit(widenlattice(๐•ƒ)) has_extended_unionsplit(::AnyMustAliasesLattice) = true has_extended_unionsplit(::JLTypeLattice) = false # Curried versions โŠ‘(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> โŠ‘(lattice, a, b) โŠ(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> โŠ(lattice, a, b) โ‹ค(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> โ‹ค(lattice, a, b) # Fallbacks for external packages using these methods const fallback_lattice = InferenceLattice(BaseInferenceLattice.instance) const fallback_ipo_lattice = InferenceLattice(IPOResultLattice.instance) @nospecializeinfer @nospecialize(a) โŠ‘ @nospecialize(b) = โŠ‘(fallback_lattice, a, b) @nospecializeinfer @nospecialize(a) โŠ @nospecialize(b) = โŠ(fallback_lattice, a, b) @nospecializeinfer @nospecialize(a) โ‹ค @nospecialize(b) = โ‹ค(fallback_lattice, a, b) @nospecializeinfer tmeet(@nospecialize(a), @nospecialize(b)) = tmeet(fallback_lattice, a, b) @nospecializeinfer tmerge(@nospecialize(a), @nospecialize(b)) = tmerge(fallback_lattice, a, b) @nospecializeinfer is_lattice_equal(@nospecialize(a), @nospecialize(b)) = is_lattice_equal(fallback_lattice, a, b) # Widenlattice with argument widenlattice(::JLTypeLattice, @nospecialize(t)) = widenconst(t) function widenlattice(๐•ƒ::AbstractLattice, @nospecialize(t)) is_valid_lattice_norec(๐•ƒ, t) && return t widenlattice(widenlattice(๐•ƒ), t) end