Revision eb91a2ee3774e6b40581cb14ea5a2a17a1c874f1 authored by Jameson Nash on 01 February 2024, 16:01:47 UTC, committed by Simon Byrne on 27 February 2024, 05:48:15 UTC
1 parent 51429b6
inferenceresult.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance) ->
(cache_argtypes::Vector{Any}, overridden_by_const::BitVector)
Returns argument types `cache_argtypes::Vector{Any}` for `linfo` that are in the native
Julia type domain. `overridden_by_const::BitVector` is all `false` meaning that
there is no additional extended lattice information there.
matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ForwardableArgtypes) ->
(cache_argtypes::Vector{Any}, overridden_by_const::BitVector)
Returns cache-correct extended lattice argument types `cache_argtypes::Vector{Any}`
for `linfo` given some `argtypes` accompanied by `overridden_by_const::BitVector`
that marks which argument contains additional extended lattice information.
In theory, there could be a `cache` containing a matching `InferenceResult`
for the provided `linfo` and `given_argtypes`. The purpose of this function is
to return a valid value for `cache_lookup(𝕃, linfo, argtypes, cache).argtypes`,
so that we can construct cache-correct `InferenceResult`s in the first place.
"""
function matching_cache_argtypes end
function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance)
mthd = isa(linfo.def, Method) ? linfo.def::Method : nothing
cache_argtypes = most_general_argtypes(mthd, linfo.specTypes)
return cache_argtypes, falses(length(cache_argtypes))
end
struct SimpleArgtypes <: ForwardableArgtypes
argtypes::Vector{Any}
end
"""
matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::SimpleArgtypes)
The implementation for `argtypes` with general extended lattice information.
This is supposed to be used for debugging and testing or external `AbstractInterpreter`
usages and in general `matching_cache_argtypes(::MethodInstance, ::ConditionalArgtypes)`
is more preferred it can forward `Conditional` information.
"""
function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, simple_argtypes::SimpleArgtypes)
(; argtypes) = simple_argtypes
given_argtypes = Vector{Any}(undef, length(argtypes))
for i = 1:length(argtypes)
given_argtypes[i] = widenslotwrapper(argtypes[i])
end
given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo)
return pick_const_args(𝕃, linfo, given_argtypes)
end
function pick_const_args(𝕃::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any})
cache_argtypes, overridden_by_const = matching_cache_argtypes(𝕃, linfo)
return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes)
end
function pick_const_args!(𝕃::AbstractLattice, cache_argtypes::Vector{Any}, overridden_by_const::BitVector, given_argtypes::Vector{Any})
for i = 1:length(given_argtypes)
given_argtype = given_argtypes[i]
cache_argtype = cache_argtypes[i]
if !is_argtype_match(𝕃, given_argtype, cache_argtype, false)
# prefer the argtype we were given over the one computed from `linfo`
if (isa(given_argtype, PartialStruct) && isa(cache_argtype, Type) &&
!⊏(𝕃, given_argtype, cache_argtype))
# if the type information of this `PartialStruct` is less strict than
# declared method signature, narrow it down using `tmeet`
given_argtype = tmeet(𝕃, given_argtype, cache_argtype)
end
cache_argtypes[i] = given_argtype
overridden_by_const[i] = true
end
end
return cache_argtypes, overridden_by_const
end
function is_argtype_match(𝕃::AbstractLattice,
@nospecialize(given_argtype),
@nospecialize(cache_argtype),
overridden_by_const::Bool)
if is_forwardable_argtype(𝕃, given_argtype)
return is_lattice_equal(𝕃, given_argtype, cache_argtype)
end
return !overridden_by_const
end
va_process_argtypes(𝕃::AbstractLattice, given_argtypes::Vector{Any}, mi::MethodInstance) =
va_process_argtypes(Returns(nothing), 𝕃, given_argtypes, mi)
function va_process_argtypes(@specialize(va_handler!), 𝕃::AbstractLattice, given_argtypes::Vector{Any}, mi::MethodInstance)
def = mi.def
isva = isa(def, Method) ? def.isva : false
nargs = isa(def, Method) ? Int(def.nargs) : length(mi.specTypes.parameters)
if isva || isvarargtype(given_argtypes[end])
isva_given_argtypes = Vector{Any}(undef, nargs)
for i = 1:(nargs-isva)
isva_given_argtypes[i] = argtype_by_index(given_argtypes, i)
end
if isva
if length(given_argtypes) < nargs && isvarargtype(given_argtypes[end])
last = length(given_argtypes)
else
last = nargs
end
isva_given_argtypes[nargs] = tuple_tfunc(𝕃, given_argtypes[last:end])
va_handler!(isva_given_argtypes, last)
end
return isva_given_argtypes
end
@assert length(given_argtypes) == nargs "invalid `given_argtypes` for `mi`"
return given_argtypes
end
function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(specTypes),
withfirst::Bool = true)
toplevel = method === nothing
isva = !toplevel && method.isva
linfo_argtypes = Any[(unwrap_unionall(specTypes)::DataType).parameters...]
nargs::Int = toplevel ? 0 : method.nargs
# For opaque closure, the closure environment is processed elsewhere
withfirst || (nargs -= 1)
cache_argtypes = Vector{Any}(undef, nargs)
# First, if we're dealing with a varargs method, then we set the last element of `args`
# to the appropriate `Tuple` type or `PartialStruct` instance.
if !toplevel && isva
if specTypes::Type == Tuple
linfo_argtypes = Any[Any for i = 1:nargs]
if nargs > 1
linfo_argtypes[end] = Tuple
end
vargtype = Tuple
else
linfo_argtypes_length = length(linfo_argtypes)
if nargs > linfo_argtypes_length
va = linfo_argtypes[linfo_argtypes_length]
if isvarargtype(va)
new_va = rewrap_unionall(unconstrain_vararg_length(va), specTypes)
vargtype = Tuple{new_va}
else
vargtype = Tuple{}
end
else
vargtype_elements = Any[]
for i in nargs:linfo_argtypes_length
p = linfo_argtypes[i]
p = unwraptv(isvarargtype(p) ? unconstrain_vararg_length(p) : p)
push!(vargtype_elements, elim_free_typevars(rewrap_unionall(p, specTypes)))
end
for i in 1:length(vargtype_elements)
atyp = vargtype_elements[i]
if issingletontype(atyp)
# replace singleton types with their equivalent Const object
vargtype_elements[i] = Const(atyp.instance)
elseif isconstType(atyp)
vargtype_elements[i] = Const(atyp.parameters[1])
end
end
vargtype = tuple_tfunc(fallback_lattice, vargtype_elements)
end
end
cache_argtypes[nargs] = vargtype
nargs -= 1
end
# Now, we propagate type info from `linfo_argtypes` into `cache_argtypes`, improving some
# type info as we go (where possible). Note that if we're dealing with a varargs method,
# we already handled the last element of `cache_argtypes` (and decremented `nargs` so that
# we don't overwrite the result of that work here).
linfo_argtypes_length = length(linfo_argtypes)
if linfo_argtypes_length > 0
n = linfo_argtypes_length > nargs ? nargs : linfo_argtypes_length
tail_index = n
local lastatype
for i = 1:n
atyp = linfo_argtypes[i]
if i == n && isvarargtype(atyp)
atyp = unwrapva(atyp)
tail_index -= 1
end
atyp = unwraptv(atyp)
if issingletontype(atyp)
# replace singleton types with their equivalent Const object
atyp = Const(atyp.instance)
elseif isconstType(atyp)
atyp = Const(atyp.parameters[1])
else
atyp = elim_free_typevars(rewrap_unionall(atyp, specTypes))
end
i == n && (lastatype = atyp)
cache_argtypes[i] = atyp
end
for i = (tail_index + 1):nargs
cache_argtypes[i] = lastatype
end
else
@assert nargs == 0 "invalid specialization of method" # wrong number of arguments
end
cache_argtypes
end
# eliminate free `TypeVar`s in order to make the life much easier down the road:
# at runtime only `Type{...}::DataType` can contain invalid type parameters, and other
# malformed types here are user-constructed type arguments given at an inference entry
# so this function will replace only the malformed `Type{...}::DataType` with `Type`
# and simply replace other possibilities with `Any`
function elim_free_typevars(@nospecialize t)
if has_free_typevars(t)
return isType(t) ? Type : Any
else
return t
end
end
function cache_lookup(𝕃::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any}, cache::Vector{InferenceResult})
method = linfo.def::Method
nargs = Int(method.nargs)
method.isva && (nargs -= 1)
length(given_argtypes) ≥ nargs || return nothing
for cached_result in cache
cached_result.linfo === linfo || continue
cache_argtypes = cached_result.argtypes
cache_overridden_by_const = cached_result.overridden_by_const
for i in 1:nargs
if !is_argtype_match(𝕃, widenmustalias(given_argtypes[i]),
cache_argtypes[i], cache_overridden_by_const[i])
@goto next_cache
end
end
if method.isva
if !is_argtype_match(𝕃, tuple_tfunc(𝕃, given_argtypes[(nargs + 1):end]),
cache_argtypes[end], cache_overridden_by_const[end])
@goto next_cache
end
end
return cached_result
@label next_cache
end
return nothing
end
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...