mollusk 0e4acfb8f2 fix incorrect folder name for julia-0.6.x
Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
2018-06-11 03:28:36 -07:00

5444 lines
191 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# This file is a part of Julia. License is MIT: https://julialang.org/license
import Core: _apply, svec, apply_type, Builtin, IntrinsicFunction, MethodInstance
#### parameters limiting potentially-infinite types ####
const MAX_TYPEUNION_LEN = 3
const MAX_TYPE_DEPTH = 8
const MAX_INLINE_CONST_SIZE = 256
struct InferenceParams
world::UInt
# optimization
inlining::Bool
# parameters limiting potentially-infinite types (configurable)
MAX_TUPLETYPE_LEN::Int
MAX_TUPLE_DEPTH::Int
MAX_TUPLE_SPLAT::Int
MAX_UNION_SPLITTING::Int
MAX_APPLY_UNION_ENUM::Int
# reasonable defaults
function InferenceParams(world::UInt;
inlining::Bool = inlining_enabled(),
tupletype_len::Int = 15,
tuple_depth::Int = 4,
tuple_splat::Int = 16,
union_splitting::Int = 4,
apply_union_enum::Int = 8)
return new(world, inlining, tupletype_len,
tuple_depth, tuple_splat, union_splitting, apply_union_enum)
end
end
const UNION_SPLIT_MISMATCH_ERROR = false
# alloc_elim_pass! relies on `Slot_AssignedOnce | Slot_UsedUndef` being
# SSA. This should be true now but can break if we start to track conditional
# constants. e.g.
#
# cond && (a = 1)
# other_code()
# cond && use(a)
# slot property bit flags
const Slot_Assigned = 2
const Slot_AssignedOnce = 16
const Slot_UsedUndef = 32
#### inference state types ####
struct NotFound end
const NF = NotFound()
const LineNum = Int
const VarTable = Array{Any,1}
# The type of a variable load is either a value or an UndefVarError
mutable struct VarState
typ
undef::Bool
VarState(typ::ANY, undef::Bool) = new(typ, undef)
end
# The type of a value might be constant
struct Const
val
actual::Bool # if true, we obtained `val` by actually calling a @pure function
Const(v::ANY) = new(v, false)
Const(v::ANY, a::Bool) = new(v, a)
end
# The type of a value might be Bool,
# but where the value of the boolean can be used in back-propagation to
# limit the type of some other variable
# The Conditional type tracks the set of branches on variable type info
# that was used to create the boolean condition
mutable struct Conditional
var::Union{Slot,SSAValue}
vtype
elsetype
function Conditional(
var::ANY,
vtype::ANY,
nottype::ANY)
return new(var, vtype, nottype)
end
end
struct PartialTypeVar
tv::TypeVar
# N.B.: Currently unused, but would allow turning something back
# into Const, if the bounds are pulled out of this TypeVar
lb_certain::Bool
ub_certain::Bool
PartialTypeVar(tv::TypeVar, lb_certain::Bool, ub_certain::Bool) = new(tv, lb_certain, ub_certain)
end
function rewrap(t::ANY, u::ANY)
isa(t, Const) && return t
isa(t, Conditional) && return t
return rewrap_unionall(t, u)
end
mutable struct InferenceState
sp::SimpleVector # static parameters
label_counter::Int # index of the current highest label for this function
mod::Module
currpc::LineNum
# info on the state of inference and the linfo
params::InferenceParams
linfo::MethodInstance # used here for the tuple (specTypes, env, Method)
src::CodeInfo
min_valid::UInt
max_valid::UInt
nargs::Int
stmt_types::Vector{Any}
stmt_edges::Vector{Any}
# return type
bestguess #::Type
# current active instruction pointers
ip::IntSet
pc´´::Int
nstmts::Int
# current exception handler info
cur_hand #::Tuple{LineNum, Tuple{LineNum, ...}}
handler_at::Vector{Any}
n_handlers::Int
# ssavalue sparsity and restart info
ssavalue_uses::Vector{IntSet}
ssavalue_init::Vector{Any}
backedges::Vector{Tuple{InferenceState, LineNum}} # call-graph backedges connecting from callee to caller
callers_in_cycle::Vector{InferenceState}
parent::Union{Void, InferenceState}
const_api::Bool
const_ret::Bool
# TODO: put these in InferenceParams (depends on proper multi-methodcache support)
optimize::Bool
cached::Bool
inferred::Bool
dont_work_on_me::Bool
# src is assumed to be a newly-allocated CodeInfo, that can be modified in-place to contain intermediate results
function InferenceState(linfo::MethodInstance, src::CodeInfo,
optimize::Bool, cached::Bool, params::InferenceParams)
code = src.code::Array{Any,1}
nl = label_counter(code) + 1
toplevel = !isdefined(linfo, :def)
if !toplevel && isempty(linfo.sparam_vals) && !isempty(linfo.def.sparam_syms)
# linfo is unspecialized
sp = Any[]
sig = linfo.def.sig
while isa(sig,UnionAll)
push!(sp, sig.var)
sig = sig.body
end
sp = svec(sp...)
else
sp = linfo.sparam_vals
end
src.ssavaluetypes = Any[ NF for i = 1:(src.ssavaluetypes::Int) ]
n = length(code)
s_edges = Any[ () for i = 1:n ]
s_types = Any[ () for i = 1:n ]
# initial types
nslots = length(src.slotnames)
s_types[1] = Any[ VarState(Bottom, true) for i = 1:nslots ]
src.slottypes = Any[ Bottom for i = 1:nslots ]
atypes = unwrap_unionall(linfo.specTypes)
nargs::Int = toplevel ? 0 : linfo.def.nargs
la = nargs
if la > 0
if linfo.def.isva
if atypes == Tuple
if la > 1
atypes = Tuple{Any[Any for i = 1:(la - 1)]..., Tuple.parameters[1]}
end
vararg_type = Tuple
else
vararg_type = limit_tuple_depth(params, tupletype_tail(atypes, la))
vararg_type = tuple_tfunc(vararg_type) # returns a Const object, if applicable
vararg_type = rewrap(vararg_type, linfo.specTypes)
end
s_types[1][la] = VarState(vararg_type, false)
src.slottypes[la] = vararg_type
la -= 1
end
end
laty = length(atypes.parameters)
if laty > 0
if laty > la
laty = la
end
local lastatype
atail = laty
for i = 1:laty
atyp = atypes.parameters[i]
if i == laty && isvarargtype(atyp)
atyp = unwrap_unionall(atyp).parameters[1]
atail -= 1
end
if isa(atyp, TypeVar)
atyp = atyp.ub
end
if isa(atyp, DataType) && isdefined(atyp, :instance)
# replace singleton types with their equivalent Const object
atyp = Const(atyp.instance)
elseif isconstType(atyp)
atype = Const(atyp.parameters[1])
else
atyp = rewrap_unionall(atyp, linfo.specTypes)
end
i == laty && (lastatype = atyp)
s_types[1][i] = VarState(atyp, false)
src.slottypes[i] = atyp
end
for i = (atail + 1):la
s_types[1][i] = VarState(lastatype, false)
src.slottypes[i] = lastatype
end
else
@assert la == 0 # wrong number of arguments
end
ssavalue_uses = find_ssavalue_uses(code)
ssavalue_init = copy(src.ssavaluetypes::Vector{Any})
# exception handlers
cur_hand = ()
handler_at = Any[ () for i=1:n ]
n_handlers = 0
W = IntSet()
push!(W, 1) #initial pc to visit
if !toplevel
meth = linfo.def
inmodule = meth.module
else
inmodule = current_module() # toplevel thunks are inferred in the current module
end
if cached && !toplevel
min_valid = min_world(linfo.def)
max_valid = max_world(linfo.def)
else
min_valid = typemax(UInt)
max_valid = typemin(UInt)
end
frame = new(
sp, nl, inmodule, 0, params,
linfo, src, min_valid, max_valid,
nargs, s_types, s_edges,
Union{}, W, 1, n,
cur_hand, handler_at, n_handlers,
ssavalue_uses, ssavalue_init,
Vector{Tuple{InferenceState,LineNum}}(), # backedges
Vector{InferenceState}(), # callers_in_cycle
#=parent=#nothing,
false, false, optimize, cached, false, false)
return frame
end
end
function InferenceState(linfo::MethodInstance,
optimize::Bool, cached::Bool, params::InferenceParams)
# prepare an InferenceState object for inferring lambda
# create copies of the CodeInfo definition, and any fields that type-inference might modify
if linfo.def.isstaged
try
# user code might throw errors ignore them
src = get_staged(linfo)
catch
return nothing
end
else
# TODO: post-inference see if we can swap back to the original arrays?
if isa(linfo.def.source, Array{UInt8,1})
src = ccall(:jl_uncompress_ast, Any, (Any, Any), linfo.def, linfo.def.source)
else
src = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), linfo.def.source)
src.code = copy_exprargs(src.code)
src.slotnames = copy(src.slotnames)
src.slotflags = copy(src.slotflags)
end
end
return InferenceState(linfo, src, optimize, cached, params)
end
function get_staged(li::MethodInstance)
return ccall(:jl_code_for_staged, Any, (Any,), li)::CodeInfo
end
#### helper functions ####
@inline slot_id(s) = isa(s, SlotNumber) ? (s::SlotNumber).id : (s::TypedSlot).id # using a function to ensure we can infer this
# avoid cycle due to over-specializing `any` when used by inference
function _any(f::ANY, a)
for x in a
f(x) && return true
end
return false
end
function contains_is(itr, x::ANY)
for y in itr
if y === x
return true
end
end
return false
end
anymap(f::Function, a::Array{Any,1}) = Any[ f(a[i]) for i=1:length(a) ]
_topmod(sv::InferenceState) = _topmod(sv.mod)
_topmod(m::Module) = ccall(:jl_base_relative_to, Any, (Any,), m)::Module
function istopfunction(topmod, f::ANY, sym)
if isdefined(Main, :Base) && isdefined(Main.Base, sym) && isconst(Main.Base, sym) && f === getfield(Main.Base, sym)
return true
elseif isdefined(topmod, sym) && isconst(topmod, sym) && f === getfield(topmod, sym)
return true
end
return false
end
isknownlength(t::DataType) = !isvatuple(t) ||
(length(t.parameters) > 0 && isa(unwrap_unionall(t.parameters[end]).parameters[2],Int))
# t[n:end]
tupletype_tail(t::ANY, n) = Tuple{t.parameters[n:end]...}
#### type-functions for builtins / intrinsics ####
const _Type_name = Type.body.name
isType(t::ANY) = isa(t, DataType) && (t::DataType).name === _Type_name
# true if Type is inlineable as constant (is a singleton)
isconstType(t::ANY) = isType(t) && (isleaftype(t.parameters[1]) || t.parameters[1] === Union{})
iskindtype(t::ANY) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom))
const IInf = typemax(Int) # integer infinity
const n_ifunc = reinterpret(Int32, arraylen) + 1
const t_ifunc = Array{Tuple{Int, Int, Any}, 1}(n_ifunc)
const t_ffunc_key = Array{Any, 1}(0)
const t_ffunc_val = Array{Tuple{Int, Int, Any}, 1}(0)
function add_tfunc(f::IntrinsicFunction, minarg::Int, maxarg::Int, tfunc::ANY)
t_ifunc[reinterpret(Int32, f) + 1] = (minarg, maxarg, tfunc)
end
function add_tfunc(f::Function, minarg::Int, maxarg::Int, tfunc::ANY)
push!(t_ffunc_key, f)
push!(t_ffunc_val, (minarg, maxarg, tfunc))
end
add_tfunc(throw, 1, 1, (x::ANY) -> Bottom)
# the inverse of typeof_tfunc
# returns (type, isexact)
# if isexact is false, the actual runtime type may (will) be a subtype of t
function instanceof_tfunc(t::ANY)
if t === Bottom || t === typeof(Bottom)
return Bottom, true
elseif isa(t, Const)
if isa(t.val, Type)
return t.val, true
end
elseif isType(t)
tp = t.parameters[1]
return tp, !has_free_typevars(tp)
elseif isa(t, UnionAll)
t = unwrap_unionall(t)
t, isexact = instanceof_tfunc(t)
return rewrap_unionall(t, t), isexact
elseif isa(t, Union)
ta, isexact_a = instanceof_tfunc(t.a)
tb, isexact_b = instanceof_tfunc(t.b)
return Union{ta, tb}, false # at runtime, will be exactly one of these
end
return Any, false
end
bitcast_tfunc(t::ANY, x::ANY) = instanceof_tfunc(t)[1]
math_tfunc(x::ANY) = widenconst(x)
math_tfunc(x::ANY, y::ANY) = widenconst(x)
math_tfunc(x::ANY, y::ANY, z::ANY) = widenconst(x)
fptoui_tfunc(t::ANY, x::ANY) = bitcast_tfunc(t, x)
fptosi_tfunc(t::ANY, x::ANY) = bitcast_tfunc(t, x)
function fptoui_tfunc(x::ANY)
T = widenconst(x)
T === Float64 && return UInt64
T === Float32 && return UInt32
T === Float16 && return UInt16
return Any
end
function fptosi_tfunc(x::ANY)
T = widenconst(x)
T === Float64 && return Int64
T === Float32 && return Int32
T === Float16 && return Int16
return Any
end
## conversion ##
add_tfunc(bitcast, 2, 2, bitcast_tfunc)
add_tfunc(sext_int, 2, 2, bitcast_tfunc)
add_tfunc(zext_int, 2, 2, bitcast_tfunc)
add_tfunc(trunc_int, 2, 2, bitcast_tfunc)
add_tfunc(fptoui, 1, 2, fptoui_tfunc)
add_tfunc(fptosi, 1, 2, fptosi_tfunc)
add_tfunc(uitofp, 2, 2, bitcast_tfunc)
add_tfunc(sitofp, 2, 2, bitcast_tfunc)
add_tfunc(fptrunc, 2, 2, bitcast_tfunc)
add_tfunc(fpext, 2, 2, bitcast_tfunc)
## checked conversion ##
add_tfunc(checked_trunc_sint, 2, 2, bitcast_tfunc)
add_tfunc(checked_trunc_uint, 2, 2, bitcast_tfunc)
add_tfunc(check_top_bit, 1, 1, math_tfunc)
## arithmetic ##
add_tfunc(neg_int, 1, 1, math_tfunc)
add_tfunc(add_int, 2, 2, math_tfunc)
add_tfunc(sub_int, 2, 2, math_tfunc)
add_tfunc(mul_int, 2, 2, math_tfunc)
add_tfunc(sdiv_int, 2, 2, math_tfunc)
add_tfunc(udiv_int, 2, 2, math_tfunc)
add_tfunc(srem_int, 2, 2, math_tfunc)
add_tfunc(urem_int, 2, 2, math_tfunc)
add_tfunc(neg_float, 1, 1, math_tfunc)
add_tfunc(add_float, 2, 2, math_tfunc)
add_tfunc(sub_float, 2, 2, math_tfunc)
add_tfunc(mul_float, 2, 2, math_tfunc)
add_tfunc(div_float, 2, 2, math_tfunc)
add_tfunc(rem_float, 2, 2, math_tfunc)
add_tfunc(fma_float, 3, 3, math_tfunc)
add_tfunc(muladd_float, 3, 3, math_tfunc)
## fast arithmetic ##
add_tfunc(neg_float_fast, 1, 1, math_tfunc)
add_tfunc(add_float_fast, 2, 2, math_tfunc)
add_tfunc(sub_float_fast, 2, 2, math_tfunc)
add_tfunc(mul_float_fast, 2, 2, math_tfunc)
add_tfunc(div_float_fast, 2, 2, math_tfunc)
add_tfunc(rem_float_fast, 2, 2, math_tfunc)
## bitwise operators ##
add_tfunc(and_int, 2, 2, math_tfunc)
add_tfunc(or_int, 2, 2, math_tfunc)
add_tfunc(xor_int, 2, 2, math_tfunc)
add_tfunc(not_int, 1, 1, math_tfunc)
add_tfunc(shl_int, 2, 2, math_tfunc)
add_tfunc(lshr_int, 2, 2, math_tfunc)
add_tfunc(ashr_int, 2, 2, math_tfunc)
add_tfunc(bswap_int, 1, 1, math_tfunc)
add_tfunc(ctpop_int, 1, 1, math_tfunc)
add_tfunc(ctlz_int, 1, 1, math_tfunc)
add_tfunc(cttz_int, 1, 1, math_tfunc)
add_tfunc(checked_sdiv_int, 2, 2, math_tfunc)
add_tfunc(checked_udiv_int, 2, 2, math_tfunc)
add_tfunc(checked_srem_int, 2, 2, math_tfunc)
add_tfunc(checked_urem_int, 2, 2, math_tfunc)
## functions ##
add_tfunc(abs_float, 1, 1, math_tfunc)
add_tfunc(copysign_float, 2, 2, math_tfunc)
add_tfunc(flipsign_int, 2, 2, math_tfunc)
add_tfunc(ceil_llvm, 1, 1, math_tfunc)
add_tfunc(floor_llvm, 1, 1, math_tfunc)
add_tfunc(trunc_llvm, 1, 1, math_tfunc)
add_tfunc(rint_llvm, 1, 1, math_tfunc)
add_tfunc(sqrt_llvm, 1, 1, math_tfunc)
add_tfunc(sqrt_llvm_fast, 1, 1, math_tfunc)
## same-type comparisons ##
cmp_tfunc(x::ANY, y::ANY) = Bool
add_tfunc(eq_int, 2, 2, cmp_tfunc)
add_tfunc(ne_int, 2, 2, cmp_tfunc)
add_tfunc(slt_int, 2, 2, cmp_tfunc)
add_tfunc(ult_int, 2, 2, cmp_tfunc)
add_tfunc(sle_int, 2, 2, cmp_tfunc)
add_tfunc(ule_int, 2, 2, cmp_tfunc)
add_tfunc(eq_float, 2, 2, cmp_tfunc)
add_tfunc(ne_float, 2, 2, cmp_tfunc)
add_tfunc(lt_float, 2, 2, cmp_tfunc)
add_tfunc(le_float, 2, 2, cmp_tfunc)
add_tfunc(fpiseq, 2, 2, cmp_tfunc)
add_tfunc(fpislt, 2, 2, cmp_tfunc)
add_tfunc(eq_float_fast, 2, 2, cmp_tfunc)
add_tfunc(ne_float_fast, 2, 2, cmp_tfunc)
add_tfunc(lt_float_fast, 2, 2, cmp_tfunc)
add_tfunc(le_float_fast, 2, 2, cmp_tfunc)
## checked arithmetic ##
chk_tfunc(x::ANY, y::ANY) = Tuple{widenconst(x), Bool}
add_tfunc(checked_sadd_int, 2, 2, chk_tfunc)
add_tfunc(checked_uadd_int, 2, 2, chk_tfunc)
add_tfunc(checked_ssub_int, 2, 2, chk_tfunc)
add_tfunc(checked_usub_int, 2, 2, chk_tfunc)
add_tfunc(checked_smul_int, 2, 2, chk_tfunc)
add_tfunc(checked_umul_int, 2, 2, chk_tfunc)
## other, misc intrinsics ##
add_tfunc(Core.Intrinsics.llvmcall, 3, IInf,
(fptr::ANY, rt::ANY, at::ANY, a...) -> instanceof_tfunc(rt)[1])
cglobal_tfunc(fptr::ANY) = Ptr{Void}
cglobal_tfunc(fptr::ANY, t::ANY) = (isType(t) ? Ptr{t.parameters[1]} : Ptr)
cglobal_tfunc(fptr::ANY, t::Const) = (isa(t.val, Type) ? Ptr{t.val} : Ptr)
add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc)
add_tfunc(Core.Intrinsics.select_value, 3, 3,
function (cnd::ANY, x::ANY, y::ANY)
if isa(cnd, Const)
if cnd.val === true
return x
elseif cnd.val === false
return y
else
return Bottom
end
end
(Bool cnd) || return Bottom
return tmerge(x, y)
end)
add_tfunc(===, 2, 2,
function (x::ANY, y::ANY)
if isa(x, Const) && isa(y, Const)
return Const(x.val === y.val)
elseif typeintersect(widenconst(x), widenconst(y)) === Bottom
return Const(false)
elseif (isa(x, Const) && y === typeof(x.val) && isdefined(y, :instance)) ||
(isa(y, Const) && x === typeof(y.val) && isdefined(x, :instance))
return Const(true)
elseif isa(x, Conditional) && isa(y, Const)
y.val === false && return Conditional(x.var, x.elsetype, x.vtype)
y.val === true && return x
return x
elseif isa(y, Conditional) && isa(x, Const)
x.val === false && return Conditional(y.var, y.elsetype, y.vtype)
x.val === true && return y
end
return Bool
end)
add_tfunc(isdefined, 1, IInf, (args...)->Bool)
add_tfunc(Core.sizeof, 1, 1, x->Int)
add_tfunc(nfields, 1, 1,
function (x::ANY)
isa(x,Const) && return Const(nfields(x.val))
isa(x,Conditional) && return Const(nfields(Bool))
if isType(x)
isleaftype(x.parameters[1]) && return Const(nfields(x.parameters[1]))
elseif isa(x,DataType) && !x.abstract && !(x.name === Tuple.name && isvatuple(x)) && x !== DataType
return Const(length(x.types))
end
return Int
end)
add_tfunc(Core._expr, 1, IInf, (args...)->Expr)
add_tfunc(applicable, 1, IInf, (f::ANY, args...)->Bool)
add_tfunc(Core.Intrinsics.arraylen, 1, 1, x->Int)
add_tfunc(arraysize, 2, 2, (a::ANY, d::ANY)->Int)
add_tfunc(pointerref, 3, 3,
function (a::ANY, i::ANY, align::ANY)
a = widenconst(a)
if a <: Ptr
if isa(a,DataType) && isa(a.parameters[1],Type)
return a.parameters[1]
elseif isa(a,UnionAll) && !has_free_typevars(a)
unw = unwrap_unionall(a)
if isa(unw,DataType)
return rewrap_unionall(unw.parameters[1], a)
end
end
end
return Any
end)
add_tfunc(pointerset, 4, 4, (a::ANY, v::ANY, i::ANY, align::ANY) -> a)
function typeof_tfunc(t::ANY)
if isa(t, Const)
return Const(typeof(t.val))
elseif isa(t, Conditional)
return Const(Bool)
elseif isType(t)
tp = t.parameters[1]
if !isleaftype(tp)
return DataType # typeof(Kind::Type)::DataType
else
return Const(typeof(tp)) # XXX: this is not necessarily true
end
elseif isa(t, DataType)
if isleaftype(t) || isvarargtype(t)
return Const(t)
elseif t === Any
return DataType
else
return Type{<:t}
end
elseif isa(t, Union)
a = widenconst(typeof_tfunc(t.a))
b = widenconst(typeof_tfunc(t.b))
return Union{a, b}
elseif isa(t, TypeVar) && !(Any <: t.ub)
return typeof_tfunc(t.ub)
elseif isa(t, UnionAll)
return rewrap_unionall(widenconst(typeof_tfunc(unwrap_unionall(t))), t)
else
return DataType # typeof(anything)::DataType
end
end
add_tfunc(typeof, 1, 1, typeof_tfunc)
add_tfunc(typeassert, 2, 2,
function (v::ANY, t::ANY)
t, isexact = instanceof_tfunc(t)
t === Any && return v
if isa(v, Const)
if !has_free_typevars(t) && !isa(v.val, t)
return Bottom
end
return v
elseif isa(v, Conditional)
if !(Bool <: t)
return Bottom
end
return v
end
return typeintersect(v, t)
end)
add_tfunc(isa, 2, 2,
function (v::ANY, t::ANY)
t, isexact = instanceof_tfunc(t)
if !has_free_typevars(t)
if t === Bottom
return Const(false)
elseif v t
if isexact
return Const(true)
end
elseif isa(v, Const) || isa(v, Conditional) || (isleaftype(v) && !iskindtype(v))
return Const(false)
elseif isexact && typeintersect(v, t) === Bottom
if !iskindtype(v) #= subtyping currently intentionally answers this query incorrectly for kinds =#
return Const(false)
end
end
end
# TODO: handle non-leaftype(t) by testing against lower and upper bounds
return Bool
end)
add_tfunc(<:, 2, 2,
function (a::ANY, b::ANY)
a, isexact_a = instanceof_tfunc(a)
b, isexact_b = instanceof_tfunc(b)
if !has_free_typevars(a) && !has_free_typevars(b)
if a <: b
if isexact_b || a === Bottom
return Const(true)
end
else
if isexact_a || (b !== Bottom && typeintersect(a, b) === Union{})
return Const(false)
end
end
end
return Bool
end)
function type_depth(t::ANY)
if t === Bottom
return 0
elseif isa(t, Union)
return max(type_depth(t.a), type_depth(t.b)) + 1
elseif isa(t, DataType)
return (t::DataType).depth
elseif isa(t, UnionAll)
if t.var.ub === Any && t.var.lb === Bottom
return type_depth(t.body)
end
return max(type_depth(t.var.ub)+1, type_depth(t.var.lb)+1, type_depth(t.body))
end
return 0
end
function limit_type_depth(t::ANY, d::Int)
r = limit_type_depth(t, d, true, TypeVar[])
@assert !isa(t, Type) || t <: r
return r
end
function limit_type_depth(t::ANY, d::Int, cov::Bool, vars::Vector{TypeVar}=TypeVar[])
if isa(t,Union)
if d > MAX_TYPE_DEPTH
if cov
return Any
else
var = TypeVar(:_)
push!(vars, var)
return var
end
end
return Union{limit_type_depth(t.a, d+1, cov, vars),
limit_type_depth(t.b, d+1, cov, vars)}
elseif isa(t,UnionAll)
v = t.var
if v.ub === Any
if v.lb === Bottom
return UnionAll(t.var, limit_type_depth(t.body, d, cov, vars))
end
ub = Any
else
ub = limit_type_depth(v.ub, d+1, true)
end
if v.lb === Bottom || type_depth(v.lb) > MAX_TYPE_DEPTH
# note: lower bounds need to be widened by making them lower
lb = Bottom
else
lb = v.lb
end
v2 = TypeVar(v.name, lb, ub)
return UnionAll(v2, limit_type_depth(t{v2}, d, cov, vars))
elseif !isa(t,DataType)
return t
end
P = t.parameters
isempty(P) && return t
if d > MAX_TYPE_DEPTH
if isvarargtype(t)
# never replace Vararg with non-Vararg
return Vararg{limit_type_depth(P[1], d, cov, vars), P[2]}
end
widert = t.name.wrapper
if !(t <: widert)
# This can happen when a typevar has bounds too wide for its context, e.g.
# `Complex{T} where T` is not a subtype of `Complex`. In that case widen even
# faster to something safe to ensure the result is a supertype of the input.
widert = Any
end
cov && return widert
var = TypeVar(:_, widert)
push!(vars, var)
return var
end
stillcov = cov && (t.name === Tuple.name)
Q = map(x->limit_type_depth(x, d+1, stillcov, vars), P)
R = t.name.wrapper{Q...}
if cov && !stillcov
for var in vars
R = UnionAll(var, R)
end
end
return R
end
const DataType_name_fieldindex = fieldindex(DataType, :name)
const DataType_parameters_fieldindex = fieldindex(DataType, :parameters)
const DataType_types_fieldindex = fieldindex(DataType, :types)
const DataType_super_fieldindex = fieldindex(DataType, :super)
const TypeName_name_fieldindex = fieldindex(TypeName, :name)
const TypeName_module_fieldindex = fieldindex(TypeName, :module)
const TypeName_wrapper_fieldindex = fieldindex(TypeName, :wrapper)
function const_datatype_getfield_tfunc(sv, fld)
if (fld == DataType_name_fieldindex ||
fld == DataType_parameters_fieldindex ||
fld == DataType_types_fieldindex ||
fld == DataType_super_fieldindex)
return abstract_eval_constant(getfield(sv, fld))
end
return nothing
end
function getfield_tfunc(s00::ANY, name)
if isa(s00, TypeVar)
s00 = s00.ub
end
s = unwrap_unionall(s00)
if isa(s, Union)
return tmerge(rewrap(getfield_tfunc(s.a, name),s00),
rewrap(getfield_tfunc(s.b, name),s00))
elseif isa(s, Conditional)
return Bottom # Bool has no fields
elseif isa(s, Const) || isType(s)
if !isa(s, Const)
p1 = s.parameters[1]
if !isleaftype(p1)
return Any
end
sv = p1
else
sv = s.val
end
if isa(name, Const)
nv = name.val
if isa(sv, UnionAll)
if nv === :var || nv === 1
return Const(sv.var)
elseif nv === :body || nv === 2
return Const(sv.body)
end
elseif isa(sv, DataType)
t = const_datatype_getfield_tfunc(sv, isa(nv, Symbol) ?
fieldindex(DataType, nv, false) : nv)
t !== nothing && return t
elseif isa(sv, TypeName)
fld = isa(nv, Symbol) ? fieldindex(TypeName, nv, false) : nv
if (fld == TypeName_name_fieldindex ||
fld == TypeName_module_fieldindex ||
fld == TypeName_wrapper_fieldindex)
return abstract_eval_constant(getfield(sv, fld))
end
end
if isa(sv, Module) && isa(nv, Symbol)
return abstract_eval_global(sv, nv)
end
if !(isa(nv,Symbol) || isa(nv,Int))
return Bottom
end
if (isa(sv, SimpleVector) || isimmutable(sv)) && isdefined(sv, nv)
return abstract_eval_constant(getfield(sv, nv))
end
end
s = typeof(sv)
end
if !isa(s,DataType) || s.abstract
return Any
end
if s <: Tuple && name Symbol
return Bottom
end
if s <: Module
if name Int
return Bottom
end
return Any
end
if isempty(s.types)
return Bottom
end
if isa(name, Conditional)
return Bottom # can't index fields with Bool
end
if !isa(name, Const)
if !(Int <: name || Symbol <: name)
return Bottom
end
if length(s.types) == 1
return rewrap_unionall(unwrapva(s.types[1]), s00)
end
# union together types of all fields
R = reduce(tmerge, Bottom, map(t -> rewrap_unionall(unwrapva(t), s00), s.types))
# do the same limiting as the known-symbol case to preserve type-monotonicity
if isempty(s.parameters)
return R
end
return limit_type_depth(R, 0)
end
fld = name.val
if isa(fld,Symbol)
fld = fieldindex(s, fld, false)
end
if !isa(fld,Int)
return Bottom
end
nf = length(s.types)
if s <: Tuple && fld >= nf && isvarargtype(s.types[nf])
return rewrap_unionall(unwrapva(s.types[nf]), s00)
end
if fld < 1 || fld > nf
return Bottom
end
if isType(s00) && isleaftype(s00.parameters[1])
sp = s00.parameters[1]
elseif isa(s00, Const) && isa(s00.val, DataType)
sp = s00.val
else
sp = nothing
end
if sp !== nothing
t = const_datatype_getfield_tfunc(sp, fld)
t !== nothing && return t
end
R = s.types[fld]
if isempty(s.parameters)
return R
end
# TODO jb/subtype is this still necessary?
# conservatively limit the type depth here,
# since the UnionAll type bound is otherwise incorrect
# in the current type system
return rewrap_unionall(limit_type_depth(R, 0), s00)
end
add_tfunc(getfield, 2, 2, (s::ANY, name::ANY) -> getfield_tfunc(s, name))
add_tfunc(setfield!, 3, 3, (o::ANY, f::ANY, v::ANY) -> v)
function fieldtype_tfunc(s0::ANY, name::ANY)
if s0 === Any || s0 === Type || DataType s0 || UnionAll s0
return Type
end
# fieldtype only accepts DataType and UnionAll, errors on `Module`
if isa(s0,Const) && (!(isa(s0.val,DataType) || isa(s0.val,UnionAll)) || s0.val === Module)
return Bottom
end
if s0 == Type{Module} || s0 == Type{Union{}} || isa(s0, Conditional)
return Bottom
end
s = instanceof_tfunc(s0)[1]
u = unwrap_unionall(s)
if isa(u,Union)
return tmerge(rewrap(fieldtype_tfunc(u.a, name),s),
rewrap(fieldtype_tfunc(u.b, name),s))
end
if !isa(u,DataType) || u.abstract
return Type
end
ftypes = u.types
if isempty(ftypes)
return Bottom
end
if !isa(name, Const)
if !(Int <: name || Symbol <: name)
return Bottom
end
return reduce(tmerge, Bottom,
Any[ fieldtype_tfunc(s0, Const(i)) for i = 1:length(ftypes) ])
end
fld = name.val
if isa(fld,Symbol)
fld = fieldindex(u, fld, false)
end
if !isa(fld, Int)
return Bottom
end
nf = length(ftypes)
if u.name === Tuple.name && fld >= nf && isvarargtype(ftypes[nf])
ft = unwrapva(ftypes[nf])
elseif fld < 1 || fld > nf
return Bottom
else
ft = ftypes[fld]
end
exact = (isa(s0, Const) || isType(s0)) && !has_free_typevars(s)
ft = rewrap_unionall(ft,s)
if exact
return Const(ft)
end
return Type{<:ft}
end
add_tfunc(fieldtype, 2, 2, fieldtype_tfunc)
function valid_tparam(x::ANY)
if isa(x,Tuple)
for t in x
!valid_tparam(t) && return false
end
return true
end
return isa(x,Int) || isa(x,Symbol) || isa(x,Bool) || (!isa(x,Type) && isbits(x))
end
has_free_typevars(t::ANY) = ccall(:jl_has_free_typevars, Cint, (Any,), t)!=0
# TODO: handle e.g. apply_type(T, R::Union{Type{Int32},Type{Float64}})
function apply_type_tfunc(headtypetype::ANY, args::ANY...)
if isa(headtypetype, Const)
headtype = headtypetype.val
elseif isType(headtypetype) && isleaftype(headtypetype.parameters[1])
headtype = headtypetype.parameters[1]
else
return Any
end
largs = length(args)
if headtype === Union
largs == 0 && return Const(Bottom)
largs == 1 && return args[1]
for i = 1:largs
ai = args[i]
if !isa(ai, Const) || !isa(ai.val, Type)
if !isType(ai)
return Any
end
end
end
ty = Union{}
allconst = true
for i = 1:largs
ai = args[i]
if isType(ai)
aty = ai.parameters[1]
isleaftype(aty) || (allconst = false)
else
aty = (ai::Const).val
end
ty = Union{ty, aty}
end
return allconst ? Const(ty) : Type{ty}
end
istuple = (headtype == Tuple)
if !istuple && !isa(headtype, UnionAll)
# TODO: return `Bottom` for trying to apply a non-UnionAll
return Any
end
uncertain = false
canconst = true
tparams = Any[]
outervars = Any[]
for i = 1:largs
ai = args[i]
if isType(ai)
aip1 = ai.parameters[1]
canconst &= !has_free_typevars(aip1)
push!(tparams, aip1)
elseif isa(ai, Const) && (isa(ai.val, Type) || isa(ai.val, TypeVar) || valid_tparam(ai.val))
push!(tparams, ai.val)
elseif isa(ai, PartialTypeVar)
canconst = false
push!(tparams, ai.tv)
else
# TODO: return `Bottom` for trying to apply a non-UnionAll
uncertain = true
# These blocks improve type info but make compilation a bit slower.
# XXX
#unw = unwrap_unionall(ai)
#isT = isType(unw)
#if isT && isa(ai,UnionAll) && contains_is(outervars, ai.var)
# ai = rename_unionall(ai)
# unw = unwrap_unionall(ai)
#end
if istuple
if i == largs
push!(tparams, Vararg)
# XXX
#elseif isT
# push!(tparams, rewrap_unionall(unw.parameters[1], ai))
else
push!(tparams, Any)
end
# XXX
#elseif isT
# push!(tparams, unw.parameters[1])
# while isa(ai, UnionAll)
# push!(outervars, ai.var)
# ai = ai.body
# end
else
v = TypeVar(:_)
push!(tparams, v)
push!(outervars, v)
end
end
end
local appl
try
appl = apply_type(headtype, tparams...)
catch ex
# type instantiation might fail if one of the type parameters
# doesn't match, which could happen if a type estimate is too coarse
return Type{<:headtype}
end
!uncertain && canconst && return Const(appl)
if isvarargtype(headtype)
return Type
end
if uncertain && type_too_complex(appl,0)
return Type{<:headtype}
end
if istuple
return Type{<:appl}
end
ans = Type{appl}
for i = length(outervars):-1:1
ans = UnionAll(outervars[i], ans)
end
return ans
end
add_tfunc(apply_type, 1, IInf, apply_type_tfunc)
@pure function type_typeof(v::ANY)
if isa(v, Type)
return Type{v}
end
return typeof(v)
end
function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState)
if !isleaftype(Type{types})
return Any
end
argtype = typeintersect(types,limit_tuple_type(argtype, sv.params))
if argtype === Bottom
return Bottom
end
ft = type_typeof(f)
types = Tuple{ft, types.parameters...}
argtype = Tuple{ft, argtype.parameters...}
entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, sv.params.world)
if entry === nothing
return Any
end
meth = entry.func
(ti, env) = ccall(:jl_match_method, Ref{SimpleVector}, (Any, Any),
argtype, meth.sig)
rt, edge = typeinf_edge(meth::Method, ti, env, sv)
edge !== nothing && add_backedge!(edge::MethodInstance, sv)
return rt
end
function tuple_tfunc(argtype::ANY)
if isa(argtype, DataType) && argtype.name === Tuple.name
p = Any[ isType(x) && !isa(x.parameters[1], TypeVar) ? typeof(x.parameters[1]) : x
for x in argtype.parameters ]
t = Tuple{p...}
# replace a singleton type with its equivalent Const object
isdefined(t, :instance) && return Const(t.instance)
return t
end
return argtype
end
function builtin_tfunction(f::ANY, argtypes::Array{Any,1},
sv::Union{InferenceState,Void}, params::InferenceParams = sv.params)
isva = !isempty(argtypes) && isvarargtype(argtypes[end])
if f === tuple
for a in argtypes
if !isa(a, Const)
return tuple_tfunc(limit_tuple_depth(params, argtypes_to_type(argtypes)))
end
end
return Const(tuple(anymap(a->a.val, argtypes)...))
elseif f === svec
return SimpleVector
elseif f === arrayset
if length(argtypes) < 3 && !isva
return Bottom
end
a1 = argtypes[1]
if isvarargtype(a1)
return unwrap_unionall(a1).parameters[1]
end
return a1
elseif f === arrayref
if length(argtypes) < 2 && !isva
return Bottom
end
a = widenconst(argtypes[1])
if a <: Array
if isa(a,DataType) && (isa(a.parameters[1],Type) || isa(a.parameters[1],TypeVar))
# TODO: the TypeVar case should not be needed here
a = a.parameters[1]
return isa(a,TypeVar) ? a.ub : a
elseif isa(a,UnionAll) && !has_free_typevars(a)
unw = unwrap_unionall(a)
if isa(unw,DataType)
return rewrap_unionall(unw.parameters[1], a)
end
end
end
return Any
elseif f === Expr
if length(argtypes) < 1 && !isva
return Bottom
end
return Expr
elseif f === invoke
if length(argtypes)>1 && isa(argtypes[1], Const)
af = argtypes[1].val
sig = argtypes[2]
if isa(sig, Const)
sigty = sig.val
elseif isType(sig)
sigty = sig.parameters[1]
else
sigty = nothing
end
if isa(sigty, Type) && sigty <: Tuple && sv !== nothing
return invoke_tfunc(af, sigty, argtypes_to_type(argtypes[3:end]), sv)
end
end
return Any
end
if isva
return Any
end
if isa(f, IntrinsicFunction)
iidx = Int(reinterpret(Int32, f::IntrinsicFunction)) + 1
if iidx < 0 || iidx > length(t_ifunc)
# invalid intrinsic
return Any
end
tf = t_ifunc[iidx]
else
fidx = findfirst(t_ffunc_key, f)
if fidx == 0
# unknown/unhandled builtin function
return Any
end
tf = t_ffunc_val[fidx]
end
tf = tf::Tuple{Int, Int, Any}
if !(tf[1] <= length(argtypes) <= tf[2])
# wrong # of args
return Bottom
end
return tf[3](argtypes...)
end
limit_tuple_depth(params::InferenceParams, t::ANY) = limit_tuple_depth_(params,t,0)
function limit_tuple_depth_(params::InferenceParams, t::ANY, d::Int)
if isa(t,Union)
# also limit within Union types.
# may have to recur into other stuff in the future too.
return Union{limit_tuple_depth_(params, t.a, d+1),
limit_tuple_depth_(params, t.b, d+1)}
elseif isa(t,UnionAll)
ub = limit_tuple_depth_(params, t.var.ub, d)
if ub !== t.var.ub
var = TypeVar(t.var.name, t.var.lb, ub)
body = t{var}
else
var = t.var
body = t.body
end
body = limit_tuple_depth_(params, body, d)
return UnionAll(var, body)
elseif !(isa(t,DataType) && t.name === Tuple.name)
return t
elseif d > params.MAX_TUPLE_DEPTH
return Tuple
end
p = map(x->limit_tuple_depth_(params,x,d+1), t.parameters)
Tuple{p...}
end
limit_tuple_type = (t::ANY, params::InferenceParams) -> limit_tuple_type_n(t, params.MAX_TUPLETYPE_LEN)
function limit_tuple_type_n(t::ANY, lim::Int)
if isa(t,UnionAll)
return UnionAll(t.var, limit_tuple_type_n(t.body, lim))
end
p = t.parameters
n = length(p)
if n > lim
tail = reduce(typejoin, Bottom, Any[p[lim:(n-1)]..., unwrapva(p[n])])
return Tuple{p[1:(lim-1)]..., Vararg{tail}}
end
return t
end
# return an upper-bound on type `a` with type `b` removed
# such that `return <: a` && `Union{return, b} == Union{a, b}`
function typesubtract(a::ANY, b::ANY)
if a <: b
return Bottom
end
if isa(a, Union)
return Union{typesubtract(a.a, b),
typesubtract(a.b, b)}
end
return a # TODO: improve this bound?
end
#### recursing into expression ####
function abstract_call_gf_by_type(f::ANY, atype::ANY, sv::InferenceState)
tm = _topmod(sv)
# don't consider more than N methods. this trades off between
# compiler performance and generated code performance.
# typically, considering many methods means spending lots of time
# obtaining poor type information.
# It is important for N to be >= the number of methods in the error()
# function, so we can still know that error() is always Bottom.
# here I picked 4.
argtype = limit_tuple_type(atype, sv.params)
argtypes = unwrap_unionall(argtype).parameters
ft = unwrap_unionall(argtypes[1]) # TODO: ccall jl_first_argument_datatype here
isa(ft, DataType) || return Any # the function being called is unknown. can't properly handle this backedge right now
ftname = ft.name
isdefined(ftname, :mt) || return Any # not callable. should be Bottom, but can't track this backedge right now
if ftname === _Type_name
tname = ft.parameters[1]
if isa(tname, TypeVar)
tname = tname.ub
end
tname = unwrap_unionall(tname)
if !isa(tname, DataType)
# can't track the backedge to the ctor right now
# for things like Union
return Any
end
end
min_valid = UInt[typemin(UInt)]
max_valid = UInt[typemax(UInt)]
applicable = _methods_by_ftype(argtype, 4, sv.params.world, min_valid, max_valid)
rettype = Bottom
if applicable === false
# this means too many methods matched
return Any
end
applicable = applicable::Array{Any,1}
fullmatch = false
for (m::SimpleVector) in applicable
sig = m[1]
sigtuple = unwrap_unionall(sig)::DataType
method = m[3]::Method
sparams = m[2]::SimpleVector
recomputesvec = false
if !fullmatch && (argtype <: method.sig)
fullmatch = true
end
# limit argument type tuple growth
msig = unwrap_unionall(method.sig)
lsig = length(msig.parameters)
ls = length(sigtuple.parameters)
td = type_depth(sig)
mightlimitlength = ls > lsig + 1
mightlimitdepth = td > 2
limitlength = false
if mightlimitlength || mightlimitdepth
# TODO: FIXME: this heuristic depends on non-local state making type-inference unpredictable
cyclei = 0
infstate = sv
while infstate !== nothing
infstate = infstate::InferenceState
if isdefined(infstate.linfo, :def) && method === infstate.linfo.def
if mightlimitlength && ls > length(unwrap_unionall(infstate.linfo.specTypes).parameters)
limitlength = true
end
if mightlimitdepth && td > type_depth(infstate.linfo.specTypes)
# impose limit if we recur and the argument types grow beyond MAX_TYPE_DEPTH
if td > MAX_TYPE_DEPTH
sig = limit_type_depth(sig, 0)
sigtuple = unwrap_unionall(sig)
recomputesvec = true
break
else
p1, p2 = sigtuple.parameters, unwrap_unionall(infstate.linfo.specTypes).parameters
if length(p2) == ls
limitdepth = false
newsig = Vector{Any}(ls)
for i = 1:ls
if p1[i] <: Function && type_depth(p1[i]) > type_depth(p2[i]) &&
isa(p1[i],DataType)
# if a Function argument is growing (e.g. nested closures)
# then widen to the outermost function type. without this
# inference fails to terminate on do_quadgk.
newsig[i] = p1[i].name.wrapper
limitdepth = true
else
newsig[i] = limit_type_depth(p1[i], 1)
end
end
if limitdepth
sigtuple = Tuple{newsig...}
sig = rewrap_unionall(sigtuple, sig)
recomputesvec = true
break
end
end
end
end
end
# iterate through the cycle before walking to the parent
if cyclei < length(infstate.callers_in_cycle)
cyclei += 1
infstate = infstate.callers_in_cycle[cyclei]
else
cyclei = 0
infstate = infstate.parent
end
end
end
# limit length based on size of definition signature.
# for example, given function f(T, Any...), limit to 3 arguments
# instead of the default (MAX_TUPLETYPE_LEN)
if limitlength
if !istopfunction(tm, f, :promote_typeof)
fst = sigtuple.parameters[lsig + 1]
allsame = true
# allow specializing on longer arglists if all the trailing
# arguments are the same, since there is no exponential
# blowup in this case.
for i = (lsig + 2):ls
if sigtuple.parameters[i] != fst
allsame = false
break
end
end
if !allsame
sigtuple = limit_tuple_type_n(sigtuple, lsig + 1)
sig = rewrap_unionall(sigtuple, sig)
recomputesvec = true
end
end
end
# if sig changed, may need to recompute the sparams environment
if recomputesvec && !isempty(sparams)
recomputed = ccall(:jl_env_from_type_intersection, Ref{SimpleVector}, (Any, Any), sig, method.sig)
sig = recomputed[1]
if !isa(unwrap_unionall(sig), DataType) # probably Union{}
rettype = Any
break
end
sparams = recomputed[2]::SimpleVector
end
rt, edge = typeinf_edge(method, sig, sparams, sv)
edge !== nothing && add_backedge!(edge::MethodInstance, sv)
rettype = tmerge(rettype, rt)
if rettype === Any
break
end
end
if !(fullmatch || rettype === Any)
# also need an edge to the method table in case something gets
# added that did not intersect with any existing method
add_mt_backedge(ftname.mt, argtype, sv)
update_valid_age!(min_valid[1], max_valid[1], sv)
end
if isempty(applicable)
# TODO: this is needed because type intersection is wrong in some cases
return Any
end
#print("=> ", rettype, "\n")
return rettype
end
# determine whether `ex` abstractly evals to constant `c`
function abstract_evals_to_constant(ex::ANY, c::ANY, vtypes::VarTable, sv::InferenceState)
av = abstract_eval(ex, vtypes, sv)
return isa(av,Const) && av.val === c
end
# `typ` is the inferred type for expression `arg`.
# if the expression constructs a container (e.g. `svec(x,y,z)`),
# refine its type to an array of element types.
# Union of Tuples of the same length is converted to Tuple of Unions.
# returns an array of types
function precise_container_type(arg::ANY, typ::ANY, vtypes::VarTable, sv::InferenceState)
if isa(typ, Const)
val = typ.val
if isa(val, SimpleVector) || isa(val, Tuple)
return Any[ abstract_eval_constant(x) for x in val ]
end
end
tti0 = widenconst(typ)
tti = unwrap_unionall(tti0)
if isa(arg, Expr) && arg.head === :call && (abstract_evals_to_constant(arg.args[1], svec, vtypes, sv) ||
abstract_evals_to_constant(arg.args[1], tuple, vtypes, sv))
aa = arg.args
result = Any[ (isa(aa[j],Expr) ? aa[j].typ : abstract_eval(aa[j],vtypes,sv)) for j=2:length(aa) ]
if _any(isvarargtype, result)
return Any[Vararg{Any}]
end
return result
elseif isa(tti, Union)
utis = uniontypes(tti)
if _any(t -> !isa(t,DataType) || !(t <: Tuple) || !isknownlength(t), utis)
return Any[Vararg{Any}]
end
result = Any[rewrap_unionall(p, tti0) for p in utis[1].parameters]
for t in utis[2:end]
if length(t.parameters) != length(result)
return Any[Vararg{Any}]
end
for j in 1:length(t.parameters)
result[j] = tmerge(result[j], rewrap_unionall(t.parameters[j], tti0))
end
end
return result
elseif isa(tti0,DataType) && tti0 <: Tuple
if isvatuple(tti0) && length(tti0.parameters) == 1
return Any[Vararg{unwrapva(tti0.parameters[1])}]
else
return tti0.parameters
end
elseif tti0 <: Array
return Any[Vararg{eltype(tti0)}]
else
return Any[abstract_iteration(typ, vtypes, sv)]
end
end
# simulate iteration protocol on container type up to fixpoint
function abstract_iteration(itertype::ANY, vtypes::VarTable, sv::InferenceState)
tm = _topmod(sv)
if !isdefined(tm, :start) || !isdefined(tm, :next) || !isconst(tm, :start) || !isconst(tm, :next)
return Vararg{Any}
end
startf = getfield(tm, :start)
nextf = getfield(tm, :next)
statetype = abstract_call(startf, (), Any[Const(startf), itertype], vtypes, sv)
statetype === Bottom && return Bottom
valtype = Bottom
while valtype !== Any
nt = abstract_call(nextf, (), Any[Const(nextf), itertype, statetype], vtypes, sv)
nt = widenconst(nt)
if !isa(nt, DataType) || !(nt <: Tuple) || isvatuple(nt) || length(nt.parameters) != 2
return Vararg{Any}
end
if nt.parameters[1] <: valtype && nt.parameters[2] <: statetype
break
end
valtype = tmerge(valtype, nt.parameters[1])
statetype = tmerge(statetype, nt.parameters[2])
end
return Vararg{valtype}
end
# do apply(af, fargs...), where af is a function value
function abstract_apply(af::ANY, fargs::Vector{Any}, aargtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState)
res = Union{}
nargs = length(fargs)
assert(nargs == length(aargtypes))
splitunions = countunionsplit(aargtypes) <= sv.params.MAX_APPLY_UNION_ENUM
ctypes = Any[Any[]]
for i = 1:nargs
if aargtypes[i] === Any
# bail out completely and infer as f(::Any...)
# instead could keep what we got so far and just append a Vararg{Any} (by just
# using the normal logic from below), but that makes the time of the subarray
# test explode
ctypes = Any[Any[Vararg{Any}]]
break
end
ctypes´ = []
for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]])
cti = precise_container_type(fargs[i], ti, vtypes, sv)
for ct in ctypes
if !isempty(ct) && isvarargtype(ct[end])
tail = foldl((a,b)->tmerge(a,unwrapva(b)), unwrapva(ct[end]), cti)
push!(ctypes´, push!(ct[1:end-1], Vararg{widenconst(tail)}))
else
push!(ctypes´, append_any(ct, cti))
end
end
end
ctypes = ctypes´
end
for ct in ctypes
if length(ct) > sv.params.MAX_TUPLETYPE_LEN
tail = foldl((a,b)->tmerge(a,unwrapva(b)), Bottom, ct[sv.params.MAX_TUPLETYPE_LEN:end])
resize!(ct, sv.params.MAX_TUPLETYPE_LEN)
ct[end] = Vararg{widenconst(tail)}
end
at = append_any(Any[Const(af)], ct)
res = tmerge(res, abstract_call(af, (), at, vtypes, sv))
if res === Any
break
end
end
return res
end
function return_type_tfunc(argtypes::ANY, vtypes::VarTable, sv::InferenceState)
if length(argtypes) == 3
tt = argtypes[3]
if isa(tt, Const) || (isType(tt) && !has_free_typevars(tt))
aft = argtypes[2]
if isa(aft, Const) || (isType(aft) && !has_free_typevars(aft)) ||
(isleaftype(aft) && !(aft <: Builtin))
af_argtype = isa(tt, Const) ? tt.val : tt.parameters[1]
if isa(af_argtype, DataType) && af_argtype <: Tuple
argtypes_vec = Any[aft, af_argtype.parameters...]
astype = argtypes_to_type(argtypes_vec)
if !(aft Builtin) &&
_methods_by_ftype(astype, 0, sv.params.world,
UInt[typemin(UInt)], UInt[typemax(UInt)]) !== false
# return_type returns Bottom if no methods match, even though
# inference doesn't necessarily.
return Const(Bottom)
end
if isa(aft, Const)
rt = abstract_call(aft.val, (), argtypes_vec, vtypes, sv)
elseif isconstType(aft)
rt = abstract_call(aft.parameters[1], (), argtypes_vec, vtypes, sv)
else
rt = abstract_call_gf_by_type(nothing, astype, sv)
end
if isa(rt, Const)
# output was computed to be constant
return Const(typeof(rt.val))
elseif isleaftype(rt) || rt === Bottom
# output type was known for certain
return Const(rt)
elseif (isa(tt, Const) || isconstType(tt)) &&
(isa(aft, Const) || isconstType(aft))
# input arguments were known for certain
return Const(rt)
else
return Type{<:rt}
end
end
end
end
end
return NF
end
function pure_eval_call(f::ANY, argtypes::ANY, atype::ANY, sv::InferenceState)
for i = 2:length(argtypes)
a = argtypes[i]
if !(isa(a,Const) || isconstType(a))
return false
end
end
min_valid = UInt[typemin(UInt)]
max_valid = UInt[typemax(UInt)]
meth = _methods_by_ftype(atype, 1, sv.params.world, min_valid, max_valid)
if meth === false || length(meth) != 1
return false
end
meth = meth[1]::SimpleVector
method = meth[3]::Method
# TODO: check pure on the inferred thunk
if method.isstaged || !method.pure
return false
end
args = Any[ (a=argtypes[i]; isa(a,Const) ? a.val : a.parameters[1]) for i in 2:length(argtypes) ]
try
value = Core._apply_pure(f, args)
# TODO: add some sort of edge(s)
return Const(value, true)
catch
return false
end
end
argtypes_to_type(argtypes::Array{Any,1}) = Tuple{anymap(widenconst, argtypes)...}
_Pair_name = nothing
function Pair_name()
global _Pair_name
if _Pair_name === nothing
if isdefined(Main, :Base) && isdefined(Main.Base, :Pair)
_Pair_name = Main.Base.Pair.body.body.name
end
end
return _Pair_name
end
_typename(a) = Union{}
_typename(a::Vararg) = Any
_typename(a::TypeVar) = Any
_typename(a::DataType) = Const(a.name)
function _typename(a::Union)
ta = _typename(a.a)
tb = _typename(a.b)
ta === tb ? tb : (ta === Any || tb === Any) ? Any : Union{}
end
_typename(union::UnionAll) = _typename(union.body)
# N.B.: typename maps type equivalence classes to a single value
typename_static(t::Const) = _typename(t.val)
typename_static(t::ANY) = isType(t) ? _typename(t.parameters[1]) : Any
function abstract_call(f::ANY, fargs::Union{Tuple{},Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState)
if f === _apply
length(fargs) > 1 || return Any
aft = argtypes[2]
if isa(aft, Const)
af = aft.val
else
if isType(aft) && isleaftype(aft.parameters[1])
af = aft.parameters[1]
elseif isleaftype(aft) && isdefined(aft, :instance)
af = aft.instance
else
# TODO jb/functions: take advantage of case where non-constant `af`'s type is known
return Any
end
end
return abstract_apply(af, fargs[3:end], argtypes[3:end], vtypes, sv)
end
la = length(argtypes)
for i = 2:(la - 1)
if isvarargtype(argtypes[i])
return Any
end
end
tm = _topmod(sv)
if isa(f, Builtin) || isa(f, IntrinsicFunction)
rt = builtin_tfunction(f, argtypes[2:end], sv)
if rt === Bool && isa(fargs, Vector{Any})
# perform very limited back-propagation of type information for `is` and `isa`
if f === isa
a = fargs[2]
if isa(a, fieldtype(Conditional, :var))
aty = widenconst(argtypes[2])
tty_ub, isexact_tty = instanceof_tfunc(argtypes[3])
if isexact_tty && !isa(tty_ub, TypeVar)
tty_lb = tty_ub # TODO: this would be wrong if !isexact_tty, but instanceof_tfunc doesn't preserve this info
if !has_free_typevars(tty_lb) && !has_free_typevars(tty_ub)
ifty = typeintersect(aty, tty_ub)
elsety = typesubtract(aty, tty_lb)
if ifty != elsety
return Conditional(a, ifty, elsety)
end
end
end
return Bool
end
elseif f === (===)
a = fargs[2]
b = fargs[3]
aty = argtypes[2]
bty = argtypes[3]
# if doing a comparison to a singleton, consider returning a `Conditional` instead
if isa(aty, Const) && isa(b, fieldtype(Conditional, :var))
if isdefined(typeof(aty.val), :instance) # can only widen a if it is a singleton
return Conditional(b, aty, typesubtract(widenconst(bty), typeof(aty.val)))
end
return Conditional(b, aty, bty)
end
if isa(bty, Const) && isa(a, fieldtype(Conditional, :var))
if isdefined(typeof(bty.val), :instance) # same for b
return Conditional(a, bty, typesubtract(widenconst(aty), typeof(bty.val)))
end
return Conditional(a, bty, aty)
end
end
end
return isa(rt, TypeVar) ? rt.ub : rt
elseif f === Core.kwfunc
if length(fargs) == 2
ft = widenconst(argtypes[2])
if isa(ft, DataType) && isdefined(ft.name, :mt) && isdefined(ft.name.mt, :kwsorter)
return Const(ft.name.mt.kwsorter)
end
end
return Any
elseif f === TypeVar
lb = Union{}
ub = Any
ub_certain = lb_certain = true
if length(fargs) >= 2 && isa(argtypes[2], Const)
nv = argtypes[2].val
ubidx = 3
if length(fargs) >= 4
ubidx = 4
if isa(argtypes[3], Const)
lb = argtypes[3].val
elseif isType(argtypes[3])
lb = argtypes[3].parameters[1]
lb_certain = false
else
return TypeVar
end
end
if length(fargs) >= ubidx
if isa(argtypes[ubidx], Const)
ub = argtypes[ubidx].val
elseif isType(argtypes[ubidx])
ub = argtypes[ubidx].parameters[1]
ub_certain = false
else
return TypeVar
end
end
tv = TypeVar(nv, lb, ub)
return PartialTypeVar(tv, lb_certain, ub_certain)
end
return TypeVar
elseif f === UnionAll
if length(fargs) == 3
canconst = true
if isa(argtypes[3], Const)
body = argtypes[3].val
elseif isType(argtypes[3])
body = argtypes[3].parameters[1]
canconst = false
else
return Any
end
if !isa(body, Type) && !isa(body, TypeVar)
return Any
end
has_free_typevars(body) || return body
if isa(argtypes[2], Const)
tv = argtypes[2].val
elseif isa(argtypes[2], PartialTypeVar)
ptv = argtypes[2]
tv = ptv.tv
canconst = false
else
return Any
end
!isa(tv, TypeVar) && return Any
theunion = UnionAll(tv, body)
ret = canconst ? abstract_eval_constant(theunion) : Type{theunion}
return ret
end
return Any
elseif f === return_type
rt_rt = return_type_tfunc(argtypes, vtypes, sv)
if rt_rt !== NF
return rt_rt
end
elseif length(fargs) == 2 && istopfunction(tm, f, :!)
aty = argtypes[2]
if isa(aty, Conditional)
abstract_call_gf_by_type(f, Tuple{typeof(f), Bool}, sv) # make sure we've inferred `!(::Bool)`
return Conditional(aty.var, aty.elsetype, aty.vtype)
end
elseif length(fargs) == 3 && istopfunction(tm, f, :!==)
rty = abstract_call((===), fargs, argtypes, vtypes, sv)
if isa(rty, Conditional)
return Conditional(rty.var, rty.elsetype, rty.vtype) # swap if-else
elseif isa(rty, Const)
return Const(rty.val === false)
end
return rty
elseif length(fargs) == 3 && istopfunction(tm, f, :(>:))
# swap T1 and T2 arguments and call issubtype
fargs = Any[issubtype, fargs[3], fargs[2]]
argtypes = Any[typeof(issubtype), argtypes[3], argtypes[2]]
rty = abstract_call(issubtype, fargs, argtypes, vtypes, sv)
return rty
end
if la>2 && argtypes[3] Int
at2 = widenconst(argtypes[2])
if la==3 && at2 <: SimpleVector && istopfunction(tm, f, :getindex)
if isa(argtypes[2], Const) && isa(argtypes[3], Const)
svecval = argtypes[2].val
idx = argtypes[3].val
if isa(idx, Int) && 1 <= idx <= length(svecval) &&
isassigned(svecval, idx)
return Const(getindex(svecval, idx))
end
end
elseif (at2 <: Tuple ||
(isa(at2, DataType) && (at2::DataType).name === Pair_name()))
# allow tuple indexing functions to take advantage of constant
# index arguments.
if istopfunction(tm, f, :getindex) && la==3
return getfield_tfunc(argtypes[2], argtypes[3])
elseif istopfunction(tm, f, :next) && la==3
t1 = widenconst(getfield_tfunc(argtypes[2], argtypes[3]))
return t1===Bottom ? Bottom : Tuple{t1, Int}
elseif istopfunction(tm, f, :indexed_next) && la==4
t1 = widenconst(getfield_tfunc(argtypes[2], argtypes[3]))
return t1===Bottom ? Bottom : Tuple{t1, Int}
end
end
elseif la==2 && argtypes[2] SimpleVector && istopfunction(tm, f, :length)
if isa(argtypes[2], Const)
return Const(length(argtypes[2].val))
end
end
atype = argtypes_to_type(argtypes)
t = pure_eval_call(f, argtypes, atype, sv)
t !== false && return t
if istopfunction(tm, f, :typejoin) || f === return_type
return Type # don't try to infer these function edges directly -- it won't actually come up with anything useful
elseif length(argtypes) == 2 && istopfunction(tm, f, :typename)
return typename_static(argtypes[2])
end
if sv.params.inlining
# need to model the special inliner for ^
# to ensure we have added the same edge
if isdefined(Main, :Base) &&
((isdefined(Main.Base, :^) && f === Main.Base.:^) ||
(isdefined(Main.Base, :.^) && f === Main.Base.:.^)) &&
length(argtypes) == 3 && (argtypes[3] Int32 || argtypes[3] Int64)
a1 = argtypes[2]
basenumtype = Union{corenumtype, Main.Base.Complex64, Main.Base.Complex128, Main.Base.Rational}
if a1 basenumtype
ftimes = Main.Base.:*
ta1 = widenconst(a1)
abstract_call_gf_by_type(ftimes, Tuple{typeof(ftimes), ta1, ta1}, sv)
end
end
end
return abstract_call_gf_by_type(f, atype, sv)
end
function abstract_eval_call(e::Expr, vtypes::VarTable, sv::InferenceState)
argtypes = Any[abstract_eval(a, vtypes, sv) for a in e.args]
#print("call ", e.args[1], argtypes, "\n\n")
for x in argtypes
x === Bottom && return Bottom
end
ft = argtypes[1]
if isa(ft, Const)
f = ft.val
else
if isType(ft) && isleaftype(ft.parameters[1])
f = ft.parameters[1]
elseif isleaftype(ft) && isdefined(ft, :instance)
f = ft.instance
else
for i = 2:(length(argtypes)-1)
if isvarargtype(argtypes[i])
return Any
end
end
# non-constant function, but type is known
if (isleaftype(ft) || ft <: Type) && !(ft <: Builtin) && !(ft <: IntrinsicFunction)
return abstract_call_gf_by_type(nothing, argtypes_to_type(argtypes), sv)
end
return Any
end
end
return abstract_call(f, e.args, argtypes, vtypes, sv)
end
const _Ref_name = Ref.body.name
function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState)
if isa(e, QuoteNode)
return abstract_eval_constant((e::QuoteNode).value)
elseif isa(e, SSAValue)
return abstract_eval_ssavalue(e::SSAValue, sv.src)
elseif isa(e, Slot)
return vtypes[slot_id(e)].typ
elseif isa(e, Symbol)
return abstract_eval_global(sv.mod, e)
elseif isa(e,GlobalRef)
return abstract_eval_global(e.mod, e.name)
end
if !isa(e, Expr)
return abstract_eval_constant(e)
end
e = e::Expr
if e.head === :call
t = abstract_eval_call(e, vtypes, sv)
elseif e.head === :null
t = Void
elseif e.head === :new
t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1]
for i = 2:length(e.args)
if abstract_eval(e.args[i], vtypes, sv) === Bottom
rt = Bottom
end
end
elseif e.head === :&
abstract_eval(e.args[1], vtypes, sv)
t = Any
elseif e.head === :foreigncall
rt = e.args[2]
if isdefined(sv.linfo, :def)
spsig = sv.linfo.def.sig
if isa(spsig, UnionAll)
if !isempty(sv.linfo.sparam_vals)
env = data_pointer_from_objref(sv.linfo.sparam_vals) + sizeof(Ptr{Void})
rt = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[2], spsig, env)
else
rt = rewrap_unionall(e.args[2], spsig)
end
end
end
abstract_eval(e.args[1], vtypes, sv)
for i = 3:length(e.args)
if abstract_eval(e.args[i], vtypes, sv) === Bottom
t = Bottom
end
end
if rt === Bottom
t = Bottom
elseif isa(rt, Type)
t = rt
if isa(t, DataType) && (t::DataType).name === _Ref_name
t = t.parameters[1]
if t === Any
t = Bottom # a return type of Box{Any} is invalid
end
end
for v in sv.linfo.sparam_vals
if isa(v,TypeVar)
t = UnionAll(v, t)
end
end
else
t = Any
end
elseif e.head === :static_parameter
n = e.args[1]
t = Any
if n <= length(sv.sp)
val = sv.sp[n]
if isa(val, TypeVar) && Any <: val.ub
# static param bound to typevar
# if the tvar does not refer to anything more specific than Any,
# the static param might actually be an integer, symbol, etc.
elseif has_free_typevars(val)
vs = ccall(:jl_find_free_typevars, Any, (Any,), val)
t = Type{val}
for v in vs
t = UnionAll(v, t)
end
else
t = abstract_eval_constant(val)
end
end
elseif e.head === :method
t = (length(e.args) == 1) ? Any : Void
elseif e.head === :copyast
t = abstract_eval(e.args[1], vtypes, sv)
elseif e.head === :inert
return abstract_eval_constant(e.args[1])
elseif e.head === :invoke
error("type inference data-flow error: tried to double infer a function")
else
t = Any
end
if isa(t, TypeVar)
# no need to use a typevar as the type of an expression
t = t.ub
end
if isa(t, DataType) && isdefined(t, :instance)
# replace singleton types with their equivalent Const object
t = Const(t.instance)
end
if isa(t, Conditional)
e.typ = Bool
else
e.typ = t
end
return t
end
const abstract_eval_constant = Const
function abstract_eval_global(M::Module, s::Symbol)
if isdefined(M,s) && isconst(M,s)
return abstract_eval_constant(getfield(M,s))
end
return Any
end
function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo)
typ = src.ssavaluetypes[s.id + 1]
if typ === NF
return Bottom
end
return typ
end
#### handling for statement-position expressions ####
mutable struct StateUpdate
var::Union{Slot,SSAValue}
vtype::VarState
state::VarTable
end
function abstract_interpret(e::ANY, vtypes::VarTable, sv::InferenceState)
!isa(e, Expr) && return vtypes
# handle assignment
if e.head === :(=)
t = abstract_eval(e.args[2], vtypes, sv)
t === Bottom && return ()
lhs = e.args[1]
if isa(lhs, Slot) || isa(lhs, SSAValue)
# don't bother for GlobalRef
return StateUpdate(lhs, VarState(t, false), vtypes)
end
elseif e.head === :call || e.head === :foreigncall
t = abstract_eval(e, vtypes, sv)
t === Bottom && return ()
elseif e.head === :gotoifnot
t = abstract_eval(e.args[1], vtypes, sv)
t === Bottom && return ()
elseif e.head === :method
fname = e.args[1]
if isa(fname, Slot)
return StateUpdate(fname, VarState(Any, false), vtypes)
end
end
return vtypes
end
function type_too_complex(t::ANY, d)
if d > MAX_TYPE_DEPTH
return true
elseif isa(t,Union)
return type_too_complex(t.a, d+1) || type_too_complex(t.b, d+1)
elseif isa(t,TypeVar)
return type_too_complex(t.lb,d+1) || type_too_complex(t.ub,d+1)
elseif isa(t,UnionAll)
return type_too_complex(t.var, d) || type_too_complex(t.body, d)
elseif isa(t,DataType)
for x in (t.parameters)::SimpleVector
if type_too_complex(x, d+1)
return true
end
end
end
return false
end
## lattice operators
function issubconditional(a::Conditional, b::Conditional)
avar = a.var
bvar = b.var
if (isa(avar, Slot) && isa(bvar, Slot) && slot_id(avar) === slot_id(bvar)) ||
(isa(avar, SSAValue) && isa(bvar, SSAValue) && avar === bvar)
if a.vtype b.vtype
if a.elsetype b.elsetype
return true
end
end
end
return false
end
function (a::ANY, b::ANY)
a === NF && return true
b === NF && return false
if isa(a, Conditional)
if isa(b, Conditional)
return issubconditional(a, b)
end
a = Bool
elseif isa(b, Conditional)
return a === Bottom
end
if isa(a, Const)
if isa(b, Const)
return a.val === b.val
end
return isa(a.val, widenconst(b))
elseif isa(b, Const)
return a === Bottom
elseif !(isa(a, Type) || isa(a, TypeVar)) ||
!(isa(b, Type) || isa(b, TypeVar))
return a === b
else
return a <: b
end
end
widenconst(c::Conditional) = Bool
function widenconst(c::Const)
if isa(c.val, Type)
if isvarargtype(c.val)
return Type
end
return Type{c.val}
else
return typeof(c.val)
end
end
widenconst(c::PartialTypeVar) = TypeVar
widenconst(t::ANY) = t
issubstate(a::VarState, b::VarState) = (a.typ b.typ && a.undef <= b.undef)
# Meta expression head, these generally can't be deleted even when they are
# in a dead branch but can be ignored when analyzing uses/liveness.
is_meta_expr_head(head::Symbol) =
(head === :inbounds || head === :boundscheck || head === :meta ||
head === :line || head === :simdloop)
is_meta_expr(ex::Expr) = is_meta_expr_head(ex.head)
function tmerge(typea::ANY, typeb::ANY)
typea typeb && return typeb
typeb typea && return typea
if isa(typea, Conditional) && isa(typeb, Conditional)
if typea.var === typeb.var
vtype = tmerge(typea.vtype, typeb.vtype)
elsetype = tmerge(typea.elsetype, typeb.elsetype)
if vtype != elsetype
return Conditional(typea.var, vtype, elsetype)
end
end
return Bool
end
typea, typeb = widenconst(typea), widenconst(typeb)
typea === typeb && return typea
if !(isa(typea,Type) || isa(typea,TypeVar)) || !(isa(typeb,Type) || isa(typeb,TypeVar))
return Any
end
if (typea <: Tuple) && (typeb <: Tuple)
if isa(typea, DataType) && isa(typeb, DataType) && length(typea.parameters) == length(typeb.parameters) && !isvatuple(typea) && !isvatuple(typeb)
return typejoin(typea, typeb)
end
if isa(typea, Union) || isa(typeb, Union) || (isa(typea,DataType) && length(typea.parameters)>3) ||
(isa(typeb,DataType) && length(typeb.parameters)>3)
# widen tuples faster (see #6704), but not too much, to make sure we can infer
# e.g. (t::Union{Tuple{Bool},Tuple{Bool,Int}})[1]
return Tuple
end
end
u = Union{typea, typeb}
if unionlen(u) > MAX_TYPEUNION_LEN || type_too_complex(u, 0)
# don't let type unions get too big
# TODO: something smarter, like a common supertype
return Any
end
return u
end
function smerge(sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState})
sa === sb && return sa
sa === NF && return sb
sb === NF && return sa
issubstate(sa, sb) && return sb
issubstate(sb, sa) && return sa
return VarState(tmerge(sa.typ, sb.typ), sa.undef | sb.undef)
end
@inline tchanged(n::ANY, o::ANY) = o === NF || (n !== NF && !(n o))
@inline schanged(n::ANY, o::ANY) = (n !== o) && (o === NF || (n !== NF && !issubstate(n, o)))
function stupdate!(state::Tuple{}, changes::StateUpdate)
newst = copy(changes.state)
if isa(changes.var, Slot)
newst[slot_id(changes.var::Slot)] = changes.vtype
end
return newst
end
function stupdate!(state::VarTable, change::StateUpdate)
if !isa(change.var, Slot)
return stupdate!(state, change.state)
end
newstate = false
changeid = slot_id(change.var::Slot)
for i = 1:length(state)
if i == changeid
newtype = change.vtype
else
newtype = change.state[i]
end
oldtype = state[i]
if schanged(newtype, oldtype)
newstate = state
state[i] = smerge(oldtype, newtype)
end
end
return newstate
end
function stupdate!(state::VarTable, changes::VarTable)
newstate = false
for i = 1:length(state)
newtype = changes[i]
oldtype = state[i]
if schanged(newtype, oldtype)
newstate = state
state[i] = smerge(oldtype, newtype)
end
end
return newstate
end
stupdate!(state::Tuple{}, changes::VarTable) = copy(changes)
stupdate!(state::Tuple{}, changes::Tuple{}) = false
function stupdate1!(state::VarTable, change::StateUpdate)
if !isa(change.var, Slot)
return false
end
i = slot_id(change.var::Slot)
newtype = change.vtype
oldtype = state[i]
if schanged(newtype, oldtype)
state[i] = smerge(oldtype, newtype)
return true
end
return false
end
#### helper functions for typeinf initialization and looping ####
function label_counter(body)
l = -1
for b in body
if isa(b,LabelNode) && (b::LabelNode).label > l
l = (b::LabelNode).label
end
end
return l
end
genlabel(sv) = LabelNode(sv.label_counter += 1)
function find_ssavalue_uses(body)
uses = IntSet[]
for line = 1:length(body)
find_ssavalue_uses(body[line], uses, line)
end
return uses
end
function find_ssavalue_uses(e::ANY, uses, line)
if isa(e,SSAValue)
id = (e::SSAValue).id + 1
while length(uses) < id
push!(uses, IntSet())
end
push!(uses[id], line)
elseif isa(e,Expr)
b = e::Expr
head = b.head
is_meta_expr_head(head) && return
if head === :(=)
if isa(b.args[1],SSAValue)
id = (b.args[1]::SSAValue).id + 1
while length(uses) < id
push!(uses, IntSet())
end
end
find_ssavalue_uses(b.args[2], uses, line)
return
end
for a in b.args
find_ssavalue_uses(a, uses, line)
end
end
end
function newvar!(sv::InferenceState, typ::ANY)
id = length(sv.src.ssavaluetypes)
push!(sv.src.ssavaluetypes, typ)
return SSAValue(id)
end
inlining_enabled() = (JLOptions().can_inline == 1)
coverage_enabled() = (JLOptions().code_coverage != 0)
# work towards converging the valid age range for sv
function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::InferenceState)
sv.min_valid = max(sv.min_valid, min_valid)
sv.max_valid = min(sv.max_valid, max_valid)
@assert !isdefined(sv.linfo, :def) || !sv.cached || sv.min_valid <= sv.params.world <= sv.max_valid "invalid age range update"
nothing
end
update_valid_age!(edge::InferenceState, sv::InferenceState) = update_valid_age!(edge.min_valid, edge.max_valid, sv)
update_valid_age!(li::MethodInstance, sv::InferenceState) = update_valid_age!(min_world(li), max_world(li), sv)
# temporarily accumulate our edges to later add as backedges in the callee
function add_backedge!(li::MethodInstance, caller::InferenceState)
isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs
if caller.stmt_edges[caller.currpc] === ()
caller.stmt_edges[caller.currpc] = []
end
push!(caller.stmt_edges[caller.currpc], li)
update_valid_age!(li, caller)
nothing
end
# temporarily accumulate our no method errors to later add as backedges in the callee method table
function add_mt_backedge(mt::MethodTable, typ::ANY, caller::InferenceState)
isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs
if caller.stmt_edges[caller.currpc] === ()
caller.stmt_edges[caller.currpc] = []
end
push!(caller.stmt_edges[caller.currpc], mt)
push!(caller.stmt_edges[caller.currpc], typ)
nothing
end
# add the real backedges now
function finalize_backedges(frame::InferenceState)
toplevel = !isdefined(frame.linfo, :def)
if !toplevel && frame.cached && frame.max_valid == typemax(UInt)
caller = frame.linfo
for edges in frame.stmt_edges
i = 1
while i <= length(edges)
to = edges[i]
if isa(to, MethodInstance)
ccall(:jl_method_instance_add_backedge, Void, (Any, Any), to, caller)
i += 1
else
typeassert(to, MethodTable)
typ = edges[i + 1]
ccall(:jl_method_table_add_backedge, Void, (Any, Any, Any), to, typ, caller)
i += 2
end
end
end
end
end
function code_for_method(method::Method, atypes::ANY, sparams::SimpleVector, world::UInt, preexisting::Bool=false)
if world < min_world(method)
return nothing
end
if method.isstaged && !isleaftype(atypes)
# don't call staged functions on abstract types.
# (see issues #8504, #10230)
# we can't guarantee that their type behavior is monotonic.
# XXX: this test is wrong if Types (such as DataType or Bottom) are present
return nothing
end
if preexisting
if method.specializations !== nothing
# check cached specializations
# for an existing result stored there
return ccall(:jl_specializations_lookup, Any, (Any, Any, UInt), method, atypes, world)
end
return nothing
end
return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), method, atypes, sparams, world)
end
function typeinf_active(linfo::MethodInstance, sv::InferenceState)
for infstate in sv.callers_in_cycle
linfo === infstate.linfo && return infstate
end
return nothing
end
function add_backedge!(frame::InferenceState, caller::InferenceState, currpc::Int)
update_valid_age!(frame, caller)
backedge = (caller, currpc)
contains_is(frame.backedges, backedge) || push!(frame.backedges, backedge)
return frame
end
# at the end, all items in b's cycle
# will now be added to a's cycle
function union_caller_cycle!(a::InferenceState, b::InferenceState)
callers_in_cycle = b.callers_in_cycle
b.parent = a.parent
b.callers_in_cycle = a.callers_in_cycle
contains_is(a.callers_in_cycle, b) || push!(a.callers_in_cycle, b)
if callers_in_cycle !== a.callers_in_cycle
for caller in callers_in_cycle
if caller !== b
caller.parent = a.parent
caller.callers_in_cycle = a.callers_in_cycle
push!(a.callers_in_cycle, caller)
end
end
end
return
end
function merge_call_chain!(parent::InferenceState, ancestor::InferenceState, child::InferenceState)
# add backedge of parent <- child
# then add all backedges of parent <- parent.parent
# and merge all of the callers into ancestor.callers_in_cycle
# and ensure that walking the parent list will get the same result (DAG) from everywhere
while true
add_backedge!(child, parent, parent.currpc)
union_caller_cycle!(ancestor, child)
child = parent
parent = child.parent
child === ancestor && break
end
end
# Walk through `linfo`'s upstream call chain, starting at `parent`. If a parent
# frame matching `linfo` is encountered, then there is a cycle in the call graph
# (i.e. `linfo` is a descendant callee of itself). Upon encountering this cycle,
# we "resolve" it by merging the call chain, which entails unioning each intermediary
# frame's `callers_in_cycle` field and adding the appropriate backedges. Finally,
# we return `linfo`'s pre-existing frame. If no cycles are found, `nothing` is
# returned instead.
function resolve_call_cycle!(linfo::MethodInstance, parent::InferenceState)
frame = parent
while isa(frame, InferenceState)
if frame.linfo === linfo
merge_call_chain!(parent, frame, frame)
return frame
end
for caller in frame.callers_in_cycle
if caller.linfo === linfo
merge_call_chain!(parent, frame, caller)
return caller
end
end
frame = frame.parent
end
return nothing
end
# build (and start inferring) the inference frame for the linfo
function typeinf_frame(linfo::MethodInstance,
optimize::Bool, cached::Bool, params::InferenceParams)
frame = InferenceState(linfo, optimize, cached, params)
frame === nothing && return nothing
cached && (linfo.inInference = true)
typeinf(frame)
return frame
end
# compute (and cache) an inferred AST and return the current best estimate of the result type
function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, caller::InferenceState)
code = code_for_method(method, atypes, sparams, caller.params.world)
code === nothing && return Any, nothing
code = code::MethodInstance
if isdefined(code, :inferred)
# return rettype if the code is already inferred
# staged functions make this hard since they have two "inferred" conditions,
# so need to check whether the code itself is also inferred
inf = code.inferred
if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred
if isdefined(code, :inferred_const)
return abstract_eval_constant(code.inferred_const), code
else
return code.rettype, code
end
end
end
frame = resolve_call_cycle!(code, caller)
if frame === nothing
code.inInference = true
frame = InferenceState(code, true, true, caller.params) # always optimize and cache edge targets
if frame === nothing
code.inInference = false
return Any, nothing
end
frame.parent = caller
typeinf(frame)
return frame.bestguess, frame.inferred ? frame.linfo : nothing
end
frame = frame::InferenceState
return frame.bestguess, nothing
end
#### entry points for inferring a MethodInstance given a type signature ####
# compute an inferred AST and return type
function typeinf_code(method::Method, atypes::ANY, sparams::SimpleVector,
optimize::Bool, cached::Bool, params::InferenceParams)
code = code_for_method(method, atypes, sparams, params.world)
code === nothing && return (nothing, nothing, Any)
return typeinf_code(code::MethodInstance, optimize, cached, params)
end
function typeinf_code(linfo::MethodInstance, optimize::Bool, cached::Bool,
params::InferenceParams)
for i = 1:2 # test-and-lock-and-test
i == 2 && ccall(:jl_typeinf_begin, Void, ())
if cached && isdefined(linfo, :inferred)
# see if this code already exists in the cache
# staged functions make this hard since they have two "inferred" conditions,
# so need to check whether the code itself is also inferred
if min_world(linfo) <= params.world <= max_world(linfo)
inf = linfo.inferred
if linfo.jlcall_api == 2
method = linfo.def
tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ())
tree.code = Any[ Expr(:return, QuoteNode(linfo.inferred_const)) ]
tree.slotnames = Any[ compiler_temp_sym for i = 1:method.nargs ]
tree.slotflags = UInt8[ 0 for i = 1:method.nargs ]
tree.slottypes = nothing
tree.ssavaluetypes = 0
tree.inferred = true
tree.pure = true
tree.inlineable = true
i == 2 && ccall(:jl_typeinf_end, Void, ())
return svec(linfo, tree, linfo.rettype)
elseif isa(inf, CodeInfo)
if inf.inferred
i == 2 && ccall(:jl_typeinf_end, Void, ())
return svec(linfo, inf, linfo.rettype)
end
end
end
end
end
frame = typeinf_frame(linfo, optimize, cached, params)
ccall(:jl_typeinf_end, Void, ())
frame === nothing && return svec(nothing, nothing, Any)
frame = frame::InferenceState
frame.inferred || return svec(nothing, nothing, Any)
frame.cached || return svec(nothing, frame.src, widenconst(frame.bestguess))
return svec(frame.linfo, frame.src, widenconst(frame.bestguess))
end
# compute (and cache) an inferred AST and return the inferred return type
function typeinf_type(method::Method, atypes::ANY, sparams::SimpleVector,
cached::Bool, params::InferenceParams)
code = code_for_method(method, atypes, sparams, params.world)
code === nothing && return nothing
code = code::MethodInstance
for i = 1:2 # test-and-lock-and-test
i == 2 && ccall(:jl_typeinf_begin, Void, ())
if cached && isdefined(code, :inferred)
# see if this rettype already exists in the cache
# staged functions make this hard since they have two "inferred" conditions,
# so need to check whether the code itself is also inferred
inf = code.inferred
if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred
i == 2 && ccall(:jl_typeinf_end, Void, ())
return code.rettype
end
end
end
frame = typeinf_frame(code, cached, cached, params)
ccall(:jl_typeinf_end, Void, ())
frame === nothing && return nothing
frame = frame::InferenceState
frame.inferred || return nothing
return widenconst(frame.bestguess)
end
function typeinf_ext(linfo::MethodInstance, world::UInt)
if isdefined(linfo, :def)
# method lambda - infer this specialization via the method cache
return typeinf_code(linfo, true, true, InferenceParams(world))
else
# toplevel lambda - infer directly
ccall(:jl_typeinf_begin, Void, ())
frame = InferenceState(linfo, linfo.inferred::CodeInfo,
true, true, InferenceParams(world))
typeinf(frame)
ccall(:jl_typeinf_end, Void, ())
@assert frame.inferred # TODO: deal with this better
@assert frame.linfo === linfo
return svec(linfo, frame.src, linfo.rettype)
end
end
#### do the work of inference ####
function typeinf_work(frame::InferenceState)
@assert !frame.inferred
frame.dont_work_on_me = true # mark that this function is currently on the stack
W = frame.ip
s = frame.stmt_types
n = frame.nstmts
while frame.pc´´ <= n
# make progress on the active ip set
local pc::Int = frame.pc´´ # current program-counter
while true # inner loop optimizes the common case where it can run straight from pc to pc + 1
#print(pc,": ",s[pc],"\n")
local pc´::Int = pc + 1 # next program-counter (after executing instruction)
if pc == frame.pc´´
# need to update pc´´ to point at the new lowest instruction in W
min_pc = next(W, pc)[2]
if done(W, min_pc)
frame.pc´´ = max(min_pc, n + 1)
else
frame.pc´´ = min_pc
end
end
delete!(W, pc)
frame.currpc = pc
frame.cur_hand = frame.handler_at[pc]
frame.stmt_edges[pc] === () || empty!(frame.stmt_edges[pc])
stmt = frame.src.code[pc]
changes = abstract_interpret(stmt, s[pc]::VarTable, frame)
if changes === ()
break # this line threw an error and so there is no need to continue
# changes = s[pc]
end
if frame.cur_hand !== () && isa(changes, StateUpdate)
# propagate new type info to exception handler
# the handling for Expr(:enter) propagates all changes from before the try/catch
# so this only needs to propagate any changes
l = frame.cur_hand[1]
if stupdate1!(s[l]::VarTable, changes::StateUpdate) !== false
if l < frame.pc´´
frame.pc´´ = l
end
push!(W, l)
end
end
if isa(changes, StateUpdate)
changes_var = changes.var
if isa(changes_var, SSAValue)
# directly forward changes to an SSAValue to the applicable line
record_ssa_assign(changes_var.id + 1, changes.vtype.typ, frame)
end
elseif isa(stmt, GotoNode)
pc´ = (stmt::GotoNode).label
elseif isa(stmt, Expr)
stmt = stmt::Expr
hd = stmt.head
if hd === :gotoifnot
condt = abstract_eval(stmt.args[1], s[pc], frame)
condval = isa(condt, Const) ? condt.val : nothing
l = stmt.args[2]::Int
changes = changes::VarTable
# constant conditions
if condval === true
elseif condval === false
pc´ = l
else
# general case
frame.handler_at[l] = frame.cur_hand
if isa(condt, Conditional)
changes_else = StateUpdate(condt.var, VarState(condt.elsetype, false), changes)
changes = StateUpdate(condt.var, VarState(condt.vtype, false), changes)
else
changes_else = changes
end
newstate_else = stupdate!(s[l], changes_else)
if newstate_else !== false
# add else branch to active IP list
if l < frame.pc´´
frame.pc´´ = l
end
push!(W, l)
s[l] = newstate_else
end
end
elseif hd === :return
pc´ = n + 1
rt = abstract_eval(stmt.args[1], s[pc], frame)
if tchanged(rt, frame.bestguess)
# new (wider) return type for frame
frame.bestguess = tmerge(frame.bestguess, rt)
for (caller, caller_pc) in frame.backedges
# notify backedges of updated type information
if caller.stmt_types[caller_pc] !== ()
if caller_pc < caller.pc´´
caller.pc´´ = caller_pc
end
push!(caller.ip, caller_pc)
end
end
end
elseif hd === :enter
l = stmt.args[1]::Int
frame.cur_hand = (l, frame.cur_hand)
# propagate type info to exception handler
l = frame.cur_hand[1]
old = s[l]
new = s[pc]::Array{Any,1}
newstate_catch = stupdate!(old, new)
if newstate_catch !== false
if l < frame.pc´´
frame.pc´´ = l
end
push!(W, l)
s[l] = newstate_catch
end
typeassert(s[l], VarTable)
frame.handler_at[l] = frame.cur_hand
elseif hd === :leave
for i = 1:((stmt.args[1])::Int)
frame.cur_hand = frame.cur_hand[2]
end
end
end
pc´ > n && break # can't proceed with the fast-path fall-through
frame.handler_at[pc´] = frame.cur_hand
newstate = stupdate!(s[pc´], changes)
if isa(stmt, GotoNode) && frame.pc´´ < pc´
# if we are processing a goto node anyways,
# (such as a terminator for a loop, if-else, or try block),
# consider whether we should jump to an older backedge first,
# to try to traverse the statements in approximate dominator order
if newstate !== false
s[pc´] = newstate
end
push!(W, pc´)
pc = frame.pc´´
elseif newstate !== false
s[pc´] = newstate
pc = pc´
elseif pc´ in W
pc = pc´
else
break
end
end
end
frame.dont_work_on_me = false
end
function typeinf(frame::InferenceState)
typeinf_work(frame)
# If the current frame is part of a cycle, solve the cycle before finishing
no_active_ips_in_callers = false
while !no_active_ips_in_callers
no_active_ips_in_callers = true
for caller in frame.callers_in_cycle
caller.dont_work_on_me && return
if caller.pc´´ <= caller.nstmts # equivalent to `isempty(caller.ip)`
# Note that `typeinf_work(caller)` can potentially modify the other frames
# `frame.callers_in_cycle`, which is why making incremental progress requires the
# outer while loop.
typeinf_work(caller)
no_active_ips_in_callers = false
end
if caller.min_valid < frame.min_valid
caller.min_valid = frame.min_valid
end
if caller.max_valid > frame.max_valid
caller.max_valid = frame.max_valid
end
end
end
# with no active ip's, type inference on frame is done
if isempty(frame.callers_in_cycle)
@assert !(frame.dont_work_on_me)
frame.dont_work_on_me = true
optimize(frame)
finish(frame)
finalize_backedges(frame)
else # frame is in frame.callers_in_cycle
for caller in frame.callers_in_cycle
@assert !(caller.dont_work_on_me)
caller.dont_work_on_me = true
end
for caller in frame.callers_in_cycle
optimize(caller)
if frame.min_valid < caller.min_valid
frame.min_valid = caller.min_valid
end
if frame.max_valid > caller.max_valid
frame.max_valid = caller.max_valid
end
end
for caller in frame.callers_in_cycle
caller.min_valid = frame.min_valid
end
for caller in frame.callers_in_cycle
finish(caller)
end
for caller in frame.callers_in_cycle
finalize_backedges(caller)
end
end
nothing
end
function record_ssa_assign(ssa_id::Int, new::ANY, frame::InferenceState)
old = frame.src.ssavaluetypes[ssa_id]
if old === NF || !(new old)
frame.src.ssavaluetypes[ssa_id] = tmerge(old, new)
W = frame.ip
s = frame.stmt_types
for r in frame.ssavalue_uses[ssa_id]
if s[r] !== () # s[r] === () => unreached statement
if r < frame.pc´´
frame.pc´´ = r
end
push!(W, r)
end
end
end
nothing
end
#### finalize and record the result of running type inference ####
function isinlineable(m::Method, src::CodeInfo)
inlineable = false
cost = 1000
if m.module === _topmod(m.module)
name = m.name
sig = m.sig
if ((name === :+ || name === :* || name === :min || name === :max) &&
isa(sig,DataType) &&
sig == Tuple{sig.parameters[1],Any,Any,Any,Vararg{Any}})
inlineable = true
elseif (name === :next || name === :done || name === :unsafe_convert ||
name === :cconvert)
cost ÷= 4
end
end
if !inlineable
inlineable = inline_worthy_stmts(src.code, cost)
end
return inlineable
end
# inference completed on `me`
# now converge the optimization work
function optimize(me::InferenceState)
# annotate fulltree with type information
type_annotate!(me)
# run optimization passes on fulltree
force_noinline = false
if me.optimize
# This pass is required for the AST to be valid in codegen
# if any `SSAValue` is created by type inference. Ref issue #6068
# This (and `reindex_labels!`) needs to be run for `!me.optimize`
# if we start to create `SSAValue` in type inference when not
# optimizing and use unoptimized IR in codegen.
gotoifnot_elim_pass!(me)
inlining_pass!(me)
void_use_elim_pass!(me)
alloc_elim_pass!(me)
getfield_elim_pass!(me)
# Clean up for `alloc_elim_pass!` and `getfield_elim_pass!`
void_use_elim_pass!(me)
do_coverage = coverage_enabled()
meta_elim_pass!(me.src.code::Array{Any,1}, me.src.propagate_inbounds, do_coverage)
# Pop metadata before label reindexing
force_noinline = popmeta!(me.src.code::Array{Any,1}, :noinline)[1]
reindex_labels!(me)
end
# convert all type information into the form consumed by the code-generator
widen_all_consts!(me.src)
if isa(me.bestguess, Const) || isconstType(me.bestguess)
me.const_ret = true
proven_pure = false
# must be proven pure to use const_api; otherwise we might skip throwing errors
# (issue #20704)
# TODO: Improve this analysis; if a function is marked @pure we should really
# only care about certain errors (e.g. method errors and type errors).
if length(me.src.code) < 10
proven_pure = true
for stmt in me.src.code
if !statement_effect_free(stmt, me.src, me.mod)
proven_pure = false
break
end
end
if proven_pure
for fl in me.src.slotflags
if (fl & Slot_UsedUndef) != 0
proven_pure = false
break
end
end
end
end
if proven_pure
me.src.pure = true
end
if proven_pure && !coverage_enabled()
# use constant calling convention
# Do not emit `jlcall_api == 2` if coverage is enabled
# so that we don't need to add coverage support
# to the `jl_call_method_internal` fast path
# Still set pure flag to make sure `inference` tests pass
# and to possibly enable more optimization in the future
me.const_api = true
force_noinline || (me.src.inlineable = true)
end
end
# determine and cache inlineability
if !me.src.inlineable && !force_noinline && isdefined(me.linfo, :def)
me.src.inlineable = isinlineable(me.linfo.def, me.src)
end
me.src.inferred = true
nothing
end
# inference completed on `me`
# update the MethodInstance and notify the edges
function finish(me::InferenceState)
me.currpc = 1 # used by add_backedge
if me.cached
toplevel = !isdefined(me.linfo, :def)
if !toplevel
min_valid = me.min_valid
max_valid = me.max_valid
else
min_valid = UInt(0)
max_valid = UInt(0)
end
# check if the existing me.linfo metadata is also sufficient to describe the current inference result
# to decide if it is worth caching it again (which would also clear any generated code)
already_inferred = !me.linfo.inInference
if isdefined(me.linfo, :inferred)
inf = me.linfo.inferred
if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred
if min_world(me.linfo) == min_valid && max_world(me.linfo) == max_valid
already_inferred = true
end
end
end
if !already_inferred
const_flags = (me.const_ret) << 1 | me.const_api
if me.const_ret
if isa(me.bestguess, Const)
inferred_const = (me.bestguess::Const).val
else
@assert isconstType(me.bestguess)
inferred_const = me.bestguess.parameters[1]
end
else
inferred_const = nothing
end
if me.const_api
# use constant calling convention
inferred_result = nothing
else
inferred_result = me.src
end
if !toplevel
if !me.const_api
keeptree = me.src.inlineable || ccall(:jl_is_cacheable_sig, Int32, (Any, Any, Any),
me.linfo.specTypes, me.linfo.def.sig, me.linfo.def) != 0
if !keeptree
inferred_result = nothing
else
# compress code for non-toplevel thunks
inferred_result = ccall(:jl_compress_ast, Any, (Any, Any), me.linfo.def, inferred_result)
end
end
end
cache = ccall(:jl_set_method_inferred, Ref{MethodInstance}, (Any, Any, Any, Any, Int32, UInt, UInt),
me.linfo, widenconst(me.bestguess), inferred_const, inferred_result,
const_flags, min_valid, max_valid)
if cache !== me.linfo
me.linfo.inInference = false
me.linfo = cache
end
end
end
# update all of the callers with real backedges by traversing the temporary list of backedges
for (i, _) in me.backedges
add_backedge!(me.linfo, i)
end
# finalize and record the linfo result
me.cached && (me.linfo.inInference = false)
me.inferred = true
nothing
end
function annotate_slot_load!(e::Expr, vtypes::VarTable, sv::InferenceState, undefs::Array{Bool,1})
head = e.head
i0 = 1
if is_meta_expr_head(head) || head === :const
return
end
if head === :(=) || head === :method
i0 = 2
end
for i = i0:length(e.args)
subex = e.args[i]
if isa(subex, Expr)
annotate_slot_load!(subex, vtypes, sv, undefs)
elseif isa(subex, Slot)
id = slot_id(subex)
s = vtypes[id]
vt = widenconst(s.typ)
if s.undef
# find used-undef variables
undefs[id] = true
end
# add type annotations where needed
if !(sv.src.slottypes[id] vt)
e.args[i] = TypedSlot(id, vt)
end
end
end
end
function record_slot_assign!(sv::InferenceState)
# look at all assignments to slots
# and union the set of types stored there
# to compute a lower bound on the storage required
states = sv.stmt_types
body = sv.src.code::Vector{Any}
slottypes = sv.src.slottypes::Vector{Any}
for i = 1:length(body)
expr = body[i]
st_i = states[i]
# find all reachable assignments to locals
if isa(st_i, VarTable) && isa(expr, Expr) && expr.head === :(=)
lhs = expr.args[1]
rhs = expr.args[2]
if isa(lhs, Slot)
id = slot_id(lhs)
if isa(rhs, Slot)
# exprtype isn't yet computed for slots
vt = st_i[slot_id(rhs)].typ
else
vt = exprtype(rhs, sv.src, sv.mod)
end
vt = widenconst(vt)
if vt !== Bottom
otherTy = slottypes[id]
if otherTy === Bottom
slottypes[id] = vt
elseif otherTy === Any
slottypes[id] = Any
else
slottypes[id] = tmerge(otherTy, vt)
end
end
end
end
end
end
# annotate types of all symbols in AST
function type_annotate!(sv::InferenceState)
# remove all unused ssa values
gt = sv.src.ssavaluetypes
for i = 1:length(gt)
if gt[i] === NF
gt[i] = Union{}
end
end
# compute the required type for each slot
# to hold all of the items assigned into it
record_slot_assign!(sv)
# annotate variables load types
# remove dead code
# and compute which variables may be used undef
src = sv.src
states = sv.stmt_types
nargs = sv.nargs
nslots = length(states[1])
undefs = fill(false, nslots)
body = src.code::Array{Any,1}
nexpr = length(body)
i = 1
while i <= nexpr
st_i = states[i]
expr = body[i]
if isa(st_i, VarTable)
# st_i === () => unreached statement (see issue #7836)
if isa(expr, Expr)
annotate_slot_load!(expr, st_i, sv, undefs)
elseif isa(expr, Slot)
id = slot_id(expr)
if st_i[slot_id(expr)].undef
# find used-undef variables in statement position
undefs[id] = true
end
end
elseif sv.optimize
if ((isa(expr, Expr) && is_meta_expr(expr)) ||
isa(expr, LineNumberNode))
# keep any lexically scoped expressions
i += 1
continue
end
# This can create `Expr(:gotoifnot)` with dangling label, which we
# will clean up in `reindex_labels!`
deleteat!(body, i)
deleteat!(states, i)
nexpr -= 1
continue
end
i += 1
end
# finish marking used-undef variables
for i = 1:nslots
if undefs[i]
src.slotflags[i] |= Slot_UsedUndef
end
end
nothing
end
# widen all Const elements in type annotations
function _widen_all_consts!(e::Expr, untypedload::Vector{Bool})
e.typ = widenconst(e.typ)
for i = 1:length(e.args)
x = e.args[i]
if isa(x, Expr)
_widen_all_consts!(x, untypedload)
elseif isa(x, Slot) && (i != 1 || e.head !== :(=))
untypedload[slot_id(x)] = true
end
end
nothing
end
function widen_all_consts!(src::CodeInfo)
for i = 1:length(src.ssavaluetypes)
src.ssavaluetypes[i] = widenconst(src.ssavaluetypes[i])
end
nslots = length(src.slottypes)
untypedload = fill(false, nslots)
for i = 1:length(src.code)
x = src.code[i]
isa(x, Expr) && _widen_all_consts!(x, untypedload)
end
for i = 1:nslots
src.slottypes[i] = widen_slot_type(src.slottypes[i], untypedload[i])
end
return src
end
# widen all slots to their optimal storage layout
# we also need to preserve the type for any untyped load of a DataType
# since codegen optimizations of functions like `is` will depend on knowing it
function widen_slot_type(ty::ANY, untypedload::Bool)
ty = widenconst(ty)
if isa(ty, DataType)
if untypedload || isbits(ty) || isdefined(ty, :instance)
return ty
end
elseif isa(ty, Union)
ty_a = widen_slot_type(ty.a, false)
ty_b = widen_slot_type(ty.b, false)
if ty_a !== Any || ty_b !== Any
# TODO: better optimized codegen for unions?
return ty
end
elseif isa(ty, UnionAll)
if untypedload
return ty
end
end
return Any
end
# replace slots 1:na with argexprs, static params with spvals, and increment
# other slots by offset.
function substitute!(e::ANY, na::Int, argexprs::Vector{Any}, spsig::ANY, spvals::Vector{Any}, offset::Int)
if isa(e, Slot)
id = slot_id(e)
if 1 <= id <= na
ae = argexprs[id]
if isa(e, TypedSlot) && isa(ae, Slot)
return TypedSlot(ae.id, e.typ)
end
return ae
end
if isa(e, SlotNumber)
return SlotNumber(id + offset)
else
return TypedSlot(id + offset, e.typ)
end
end
if isa(e, NewvarNode)
return NewvarNode(substitute!(e.slot, na, argexprs, spsig, spvals, offset))
end
if isa(e, Expr)
e = e::Expr
head = e.head
if head === :static_parameter
return spvals[e.args[1]]
elseif head === :foreigncall
@assert !isa(spsig,UnionAll) || !isempty(spvals)
for i = 1:length(e.args)
if i == 2
e.args[2] = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[2], spsig, spvals)
elseif i == 3
argtuple = Any[
ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), argt, spsig, spvals)
for argt
in e.args[3] ]
e.args[3] = svec(argtuple...)
else
e.args[i] = substitute!(e.args[i], na, argexprs, spsig, spvals, offset)
end
end
elseif !is_meta_expr_head(head)
for i = 1:length(e.args)
e.args[i] = substitute!(e.args[i], na, argexprs, spsig, spvals, offset)
end
end
end
return e
end
# count occurrences up to n+1
function occurs_more(e::ANY, pred, n)
if isa(e,Expr)
e = e::Expr
head = e.head
is_meta_expr_head(head) && return 0
c = 0
for a = e.args
c += occurs_more(a, pred, n)
if c>n
return c
end
end
return c
end
if pred(e)
return 1
end
return 0
end
function exprtype(x::ANY, src::CodeInfo, mod::Module)
if isa(x, Expr)
return (x::Expr).typ
elseif isa(x, SlotNumber)
return src.slottypes[(x::SlotNumber).id]
elseif isa(x, TypedSlot)
return (x::TypedSlot).typ
elseif isa(x, SSAValue)
return abstract_eval_ssavalue(x::SSAValue, src)
elseif isa(x, Symbol)
return abstract_eval_global(mod, x::Symbol)
elseif isa(x, QuoteNode)
return abstract_eval_constant((x::QuoteNode).value)
elseif isa(x, GlobalRef)
return abstract_eval_global(x.mod, (x::GlobalRef).name)
else
return abstract_eval_constant(x)
end
end
# known affect-free calls (also effect-free)
const _pure_builtins = Any[tuple, svec, fieldtype, apply_type, ===, isa, typeof, UnionAll, nfields]
# known effect-free calls (might not be affect-free)
const _pure_builtins_volatile = Any[getfield, arrayref]
function is_pure_intrinsic(f::IntrinsicFunction)
return !(f === Intrinsics.pointerref || # this one is volatile
f === Intrinsics.pointerset || # this one is never effect-free
f === Intrinsics.llvmcall || # this one is never effect-free
f === Intrinsics.checked_trunc_sint ||
f === Intrinsics.checked_trunc_uint ||
f === Intrinsics.checked_sdiv_int ||
f === Intrinsics.checked_udiv_int ||
f === Intrinsics.checked_srem_int ||
f === Intrinsics.checked_urem_int ||
f === Intrinsics.check_top_bit ||
f === Intrinsics.sqrt_llvm ||
f === Intrinsics.cglobal) # cglobal throws an error for symbol-not-found
end
function is_pure_builtin(f::ANY)
return (contains_is(_pure_builtins, f) ||
contains_is(_pure_builtins_volatile, f) ||
(isa(f,IntrinsicFunction) && is_pure_intrinsic(f)) ||
f === return_type)
end
function statement_effect_free(e::ANY, src::CodeInfo, mod::Module)
if isa(e, Expr)
if e.head === :(=)
return !isa(e.args[1], GlobalRef) && effect_free(e.args[2], src, mod, false)
elseif e.head === :gotoifnot
return effect_free(e.args[1], src, mod, false)
end
elseif isa(e, LabelNode) || isa(e, GotoNode)
return true
end
return effect_free(e, src, mod, false)
end
# detect some important side-effect-free calls (allow_volatile=true)
# and some affect-free calls (allow_volatile=false) -- affect_free means the call
# cannot be affected by previous calls, except assignment nodes
function effect_free(e::ANY, src::CodeInfo, mod::Module, allow_volatile::Bool)
if isa(e, GlobalRef)
return (isdefined(e.mod, e.name) && (allow_volatile || isconst(e.mod, e.name)))
elseif isa(e, Symbol)
return allow_volatile
elseif isa(e, Slot)
return src.slotflags[slot_id(e)] & Slot_UsedUndef == 0
elseif isa(e, Expr)
e = e::Expr
head = e.head
if head === :static_parameter || is_meta_expr_head(head)
return true
end
if e.typ === Bottom
return false
end
ea = e.args
if head === :call
if is_known_call_p(e, is_pure_builtin, src, mod)
if !allow_volatile
if is_known_call(e, arrayref, src, mod) || is_known_call(e, arraylen, src, mod)
return false
elseif is_known_call(e, getfield, src, mod)
length(ea) == 3 || return false
et = exprtype(e, src, mod)
if !isa(et,Const) && !(isType(et) && isleaftype(et))
# first argument must be immutable to ensure e is affect_free
a = ea[2]
typ = widenconst(exprtype(a, src, mod))
if isconstType(typ)
if Const(:uid) exprtype(ea[3], src, mod)
return false # DataType uid field can change
end
elseif typ !== SimpleVector && (!isa(typ, DataType) || typ.mutable || typ.abstract)
return false
end
end
end
end
# fall-through
elseif is_known_call(e, _apply, src, mod) && length(ea) > 1
ft = exprtype(ea[2], src, mod)
if !isa(ft, Const) || !contains_is(_pure_builtins, ft.val)
return false
end
# fall-through
else
return false
end
elseif head === :new
if !allow_volatile
a = ea[1]
typ = widenconst(exprtype(a, src, mod))
if !isType(typ) || !isa((typ::Type).parameters[1],DataType) || ((typ::Type).parameters[1]::DataType).mutable
return false
end
end
# fall-through
elseif head === :return
# fall-through
elseif head === :the_exception
return allow_volatile
else
return false
end
for a in ea
if !effect_free(a, src, mod, allow_volatile)
return false
end
end
elseif isa(e, LabelNode) || isa(e, GotoNode)
return false
end
return true
end
#### post-inference optimizations ####
struct InvokeData
mt::MethodTable
entry::TypeMapEntry
types0
fexpr
texpr
end
function inline_as_constant(val::ANY, argexprs, sv::InferenceState, invoke_data::ANY)
if invoke_data === nothing
invoke_fexpr = nothing
invoke_texpr = nothing
else
invoke_data = invoke_data::InvokeData
invoke_fexpr = invoke_data.fexpr
invoke_texpr = invoke_data.texpr
end
# check if any arguments aren't effect_free and need to be kept around
stmts = invoke_fexpr === nothing ? [] : Any[invoke_fexpr]
for i = 1:length(argexprs)
arg = argexprs[i]
if !effect_free(arg, sv.src, sv.mod, false)
push!(stmts, arg)
end
if i == 1 && !(invoke_texpr === nothing)
push!(stmts, invoke_texpr)
end
end
if !is_self_quoting(val)
val = QuoteNode(val)
end
return (val, stmts)
end
function is_self_quoting(x::ANY)
return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type)
end
function countunionsplit(atypes::Vector{Any})
nu = 1
for ti in atypes
if isa(ti, Union)
nu *= unionlen(ti::Union)
end
end
return nu
end
function get_spec_lambda(atypes::ANY, sv, invoke_data::ANY)
if invoke_data === nothing
return ccall(:jl_get_spec_lambda, Any, (Any, UInt), atypes, sv.params.world)
else
invoke_data = invoke_data::InvokeData
atypes <: invoke_data.types0 || return nothing
return ccall(:jl_get_invoke_lambda, Any, (Any, Any, Any, UInt),
invoke_data.mt, invoke_data.entry, atypes, sv.params.world)
end
end
function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY,
invoke_data::ANY)
# converts a :call to :invoke
nu = countunionsplit(atypes)
nu > sv.params.MAX_UNION_SPLITTING && return NF
if invoke_data === nothing
invoke_fexpr = nothing
invoke_texpr = nothing
else
invoke_data = invoke_data::InvokeData
invoke_fexpr = invoke_data.fexpr
invoke_texpr = invoke_data.texpr
end
if nu > 1
spec_hit = nothing
spec_miss = nothing
error_label = nothing
linfo_var = add_slot!(sv.src, MethodInstance, false)
ex = Expr(:call)
ex.args = copy(argexprs)
ex.typ = etype
stmts = []
arg_hoisted = false
for i = length(atypes):-1:1
if i == 1 && !(invoke_texpr === nothing)
unshift!(stmts, invoke_texpr)
arg_hoisted = true
end
ti = atypes[i]
if arg_hoisted || isa(ti, Union)
aei = ex.args[i]
if !effect_free(aei, sv.src, sv.mod, false)
arg_hoisted = true
newvar = newvar!(sv, ti)
unshift!(stmts, :($newvar = $aei))
ex.args[i] = newvar
end
end
end
invoke_fexpr === nothing || unshift!(stmts, invoke_fexpr)
function splitunion(atypes::Vector{Any}, i::Int)
if i == 0
local sig = argtypes_to_type(atypes)
local li = get_spec_lambda(sig, sv, invoke_data)
li === nothing && return false
add_backedge!(li, sv)
local stmt = []
push!(stmt, Expr(:(=), linfo_var, li))
spec_hit === nothing && (spec_hit = genlabel(sv))
push!(stmt, GotoNode(spec_hit.label))
return stmt
else
local ti = atypes[i]
if isa(ti, Union)
local all = true
local stmts = []
local aei = ex.args[i]
for ty in uniontypes(ti::Union)
local ty
atypes[i] = ty
local match = splitunion(atypes, i - 1)
if match !== false
after = genlabel(sv)
isa_ty = Expr(:call, GlobalRef(Core, :isa), aei, ty)
isa_ty.typ = Bool
unshift!(match, Expr(:gotoifnot, isa_ty, after.label))
append!(stmts, match)
push!(stmts, after)
else
all = false
end
end
if UNION_SPLIT_MISMATCH_ERROR && all
error_label === nothing && (error_label = genlabel(sv))
push!(stmts, GotoNode(error_label.label))
else
spec_miss === nothing && (spec_miss = genlabel(sv))
push!(stmts, GotoNode(spec_miss.label))
end
atypes[i] = ti
return isempty(stmts) ? false : stmts
else
return splitunion(atypes, i - 1)
end
end
end
local match = splitunion(atypes, length(atypes))
if match !== false && spec_hit !== nothing
append!(stmts, match)
if error_label !== nothing
push!(stmts, error_label)
push!(stmts, Expr(:call, GlobalRef(_topmod(sv.mod), :error), "fatal error in type inference (type bound)"))
end
local ret_var, merge
if spec_miss !== nothing
ret_var = add_slot!(sv.src, widenconst(ex.typ), false)
merge = genlabel(sv)
push!(stmts, spec_miss)
push!(stmts, Expr(:(=), ret_var, ex))
push!(stmts, GotoNode(merge.label))
else
ret_var = newvar!(sv, ex.typ)
end
push!(stmts, spec_hit)
ex = copy(ex)
ex.head = :invoke
unshift!(ex.args, linfo_var)
push!(stmts, Expr(:(=), ret_var, ex))
if spec_miss !== nothing
push!(stmts, merge)
end
return (ret_var, stmts)
end
else
local cache_linfo = get_spec_lambda(atype_unlimited, sv, invoke_data)
cache_linfo === nothing && return NF
add_backedge!(cache_linfo, sv)
unshift!(argexprs, cache_linfo)
ex = Expr(:invoke)
ex.args = argexprs
ex.typ = etype
if invoke_texpr === nothing
if invoke_fexpr === nothing
return ex
else
return ex, Any[invoke_fexpr]
end
end
newvar = newvar!(sv, atypes[1])
stmts = Any[invoke_fexpr,
:($newvar = $(argexprs[2])),
invoke_texpr]
argexprs[2] = newvar
return ex, stmts
end
return NF
end
# inline functions whose bodies are "inline_worthy"
# where the function body doesn't contain any argument more than once.
# static parameters are ok if all the static parameter values are leaf types,
# meaning they are fully known.
# `ft` is the type of the function. `f` is the exact function if known, or else `nothing`.
# `pending_stmts` is an array of statements from functions inlined so far, so
# we can estimate the total size of the enclosing function after inlining.
function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::InferenceState,
pending_stmts)
argexprs = e.args
if (f === typeassert || ft typeof(typeassert)) && length(atypes)==3
# typeassert(x::S, T) => x, when S<:T
a3 = atypes[3]
if (isType(a3) && isleaftype(a3) && atypes[2] a3.parameters[1]) ||
(isa(a3,Const) && isa(a3.val,Type) && atypes[2] a3.val)
return (argexprs[2], ())
end
end
topmod = _topmod(sv)
# special-case inliners for known pure functions that compute types
if sv.params.inlining
if isa(e.typ, Const) # || isconstType(e.typ)
val = e.typ.val
if (f === apply_type || f === fieldtype || f === typeof || f === (===) ||
istopfunction(topmod, f, :typejoin) ||
istopfunction(topmod, f, :isbits) ||
istopfunction(topmod, f, :promote_type) ||
(f === Core.kwfunc && length(argexprs) == 2) ||
(isbits(val) && Core.sizeof(val) <= MAX_INLINE_CONST_SIZE &&
(contains_is(_pure_builtins, f) ||
(f === getfield && effect_free(e, sv.src, sv.mod, false)) ||
(isa(f,IntrinsicFunction) && is_pure_intrinsic(f)))))
return inline_as_constant(val, argexprs, sv, nothing)
end
end
end
invoke_data = nothing
invoke_fexpr = nothing
invoke_texpr = nothing
if f === Core.invoke && length(atypes) >= 3
ft = widenconst(atypes[2])
invoke_tt = widenconst(atypes[3])
if !isleaftype(ft) || !isleaftype(invoke_tt) || !isType(invoke_tt)
return NF
end
if !(isa(invoke_tt.parameters[1], Type) &&
invoke_tt.parameters[1] <: Tuple)
return NF
end
invoke_tt_params = invoke_tt.parameters[1].parameters
invoke_types = Tuple{ft, invoke_tt_params...}
invoke_entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt),
invoke_types, sv.params.world)
invoke_entry === nothing && return NF
invoke_fexpr = argexprs[1]
invoke_texpr = argexprs[3]
if effect_free(invoke_fexpr, sv.src, sv.mod, false)
invoke_fexpr = nothing
end
if effect_free(invoke_texpr, sv.src, sv.mod, false)
invoke_fexpr = nothing
end
invoke_data = InvokeData(ft.name.mt, invoke_entry,
invoke_types, invoke_fexpr, invoke_texpr)
atype0 = atypes[2]
argexpr0 = argexprs[2]
atypes = atypes[4:end]
argexprs = argexprs[4:end]
unshift!(atypes, atype0)
unshift!(argexprs, argexpr0)
f = isdefined(ft, :instance) ? ft.instance : nothing
elseif isa(f, IntrinsicFunction) || ft IntrinsicFunction ||
isa(f, Builtin) || ft Builtin
return NF
end
atype_unlimited = argtypes_to_type(atypes)
if !(invoke_data === nothing)
invoke_data = invoke_data::InvokeData
# TODO emit a type check and proceed for this case
atype_unlimited <: invoke_data.types0 || return NF
end
if !sv.params.inlining
return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited,
invoke_data)
end
if length(atypes) == 3 && istopfunction(topmod, f, :!==)
# special-case inliner for !== that precedes _methods_by_ftype union splitting
# and that works, even though inference generally avoids inferring the `!==` Method
if isa(e.typ, Const)
return inline_as_constant(e.typ.val, argexprs, sv, nothing)
end
not_is = Expr(:call, GlobalRef(Core.Intrinsics, :not_int),
Expr(:call, GlobalRef(Core, :(===)), argexprs[2], argexprs[3]))
not_is.typ = Bool
not_is.args[2].typ = Bool
return (not_is, ())
elseif length(atypes) == 3 && istopfunction(topmod, f, :(>:))
# special-case inliner for issupertype
# that works, even though inference generally avoids inferring the `>:` Method
if isa(e.typ, Const)
return inline_as_constant(e.typ.val, argexprs, sv, nothing)
end
arg_T1 = argexprs[2]
arg_T2 = argexprs[3]
issubtype_stmts = ()
if !effect_free(arg_T2, sv.src, sv.mod, false)
# spill first argument to preserve order-of-execution
issubtype_vnew = newvar!(sv, widenconst(exprtype(arg_T1, sv.src, sv.mod)))
issubtype_stmts = Any[ Expr(:(=), issubtype_vnew, arg_T1) ]
arg_T1 = issubtype_vnew
end
issubtype_expr = Expr(:call, GlobalRef(Core, :issubtype), arg_T2, arg_T1)
issubtype_expr.typ = Bool
return (issubtype_expr, issubtype_stmts)
end
if length(atype_unlimited.parameters) - 1 > sv.params.MAX_TUPLETYPE_LEN
atype = limit_tuple_type(atype_unlimited, sv.params)
else
atype = atype_unlimited
end
if invoke_data === nothing
min_valid = UInt[typemin(UInt)]
max_valid = UInt[typemax(UInt)]
meth = _methods_by_ftype(atype, 1, sv.params.world, min_valid, max_valid)
if meth === false || length(meth) != 1
return invoke_NF(argexprs, e.typ, atypes, sv,
atype_unlimited, invoke_data)
end
meth = meth[1]::SimpleVector
metharg = meth[1]::Type
methsp = meth[2]::SimpleVector
method = meth[3]::Method
else
invoke_data = invoke_data::InvokeData
method = invoke_data.entry.func
(metharg, methsp) = ccall(:jl_match_method, Ref{SimpleVector}, (Any, Any),
atype_unlimited, method.sig)
methsp = methsp::SimpleVector
end
methsig = method.sig
if !(atype <: metharg)
return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited,
invoke_data)
end
# check whether call can be inlined to just a quoted constant value
if isa(f, widenconst(ft)) && !method.isstaged
if f === return_type
if isconstType(e.typ)
return inline_as_constant(e.typ.parameters[1], argexprs, sv, invoke_data)
elseif isa(e.typ, Const)
return inline_as_constant(e.typ.val, argexprs, sv, invoke_data)
end
elseif method.pure && isa(e.typ, Const) && e.typ.actual
return inline_as_constant(e.typ.val, argexprs, sv, invoke_data)
end
end
argexprs0 = argexprs
na = Int(method.nargs)
# check for vararg function
isva = false
if na > 0 && method.isva
@assert length(argexprs) >= na - 1
# construct tuple-forming expression for argument tail
vararg = mk_tuplecall(argexprs[na:end], sv)
argexprs = Any[argexprs[1:(na - 1)]..., vararg]
isva = true
elseif na != length(argexprs)
# we have a method match only because an earlier
# inference step shortened our call args list, even
# though we have too many arguments to actually
# call this function
return NF
end
@assert na == length(argexprs)
for i = 1:length(methsp)
si = methsp[i]
isa(si, TypeVar) && return NF
end
# some gf have special tfunc, meaning they wouldn't have been inferred yet
# check the same conditions from abstract_call to detect this case
force_infer = false
if !method.isstaged
if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.module == Main.Base)
la = length(atypes)
if (la==3 && (method.name == :getindex || method.name == :next)) ||
(la==4 && method.name == :indexed_next)
if atypes[3] Int
at2 = widenconst(atypes[2])
if (at2 <: Tuple || at2 <: SimpleVector ||
(isa(at2, DataType) && (at2::DataType).name === Pair_name()))
force_infer = true
end
end
elseif la == 2 && method.name == :length && atypes[2] SimpleVector
force_infer = true
end
end
end
# see if the method has been previously inferred (and cached)
linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance}
isa(linfo, MethodInstance) || return invoke_NF(argexprs0, e.typ, atypes, sv,
atype_unlimited, invoke_data)
linfo = linfo::MethodInstance
if linfo.jlcall_api == 2
# in this case function can be inlined to a constant
add_backedge!(linfo, sv)
return inline_as_constant(linfo.inferred_const, argexprs, sv, invoke_data)
end
# see if the method has a current InferenceState frame
# or existing inferred code info
frame = nothing # Union{Void, InferenceState}
inferred = nothing # Union{Void, CodeInfo}
if force_infer && la > 2 && isa(atypes[3], Const)
# Since we inferred this with the information that atypes[3]::Const,
# must inline with that same information.
# We do that by overriding the argument type,
# while ensuring we don't cache that information
# This isn't particularly important for `getindex`,
# as we'll be able to fix that up at the end of inlinable when we verify the return type.
# But `next` and `indexed_next` make tuples which would end up burying some of that information in the AST
# where we can't easily correct it afterwards.
frame = InferenceState(linfo, #=optimize=#true, #=cache=#false, sv.params)
frame.stmt_types[1][3] = VarState(atypes[3], false)
typeinf(frame)
else
if isdefined(linfo, :inferred) && linfo.inferred !== nothing
# use cache
inferred = linfo.inferred
elseif force_infer
# create inferred code on-demand
# but if we decided in the past not to try to infer this particular signature
# (due to signature coarsening in abstract_call_gf_by_type)
# don't infer it now, as attempting to force it now would be a bad idea (non terminating)
frame = typeinf_frame(linfo, #=optimize=#true, #=cache=#true, sv.params)
end
end
# compute the return value
if isa(frame, InferenceState)
frame = frame::InferenceState
linfo = frame.linfo
inferred = frame.src
if frame.const_api # handle like jlcall_api == 2
if frame.inferred || !frame.cached
add_backedge!(frame.linfo, sv)
else
add_backedge!(frame, sv, 0)
end
if isa(frame.bestguess, Const)
inferred_const = (frame.bestguess::Const).val
else
@assert isconstType(frame.bestguess)
inferred_const = frame.bestguess.parameters[1]
end
return inline_as_constant(inferred_const, argexprs, sv, invoke_data)
end
rettype = widenconst(frame.bestguess)
else
rettype = linfo.rettype
end
# check that the code is inlineable
if inferred === nothing
src_inferred = src_inlineable = false
else
src_inferred = ccall(:jl_ast_flag_inferred, Bool, (Any,), inferred)
src_inlineable = ccall(:jl_ast_flag_inlineable, Bool, (Any,), inferred)
end
if !src_inferred || !src_inlineable
return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited,
invoke_data)
end
if isa(inferred, CodeInfo)
src = inferred
ast = copy_exprargs(inferred.code)
else
src = ccall(:jl_uncompress_ast, Any, (Any, Any), method, inferred)::CodeInfo
ast = src.code
end
ast = ast::Array{Any,1}
# `promote` is a tuple-returning function that is very important to inline
if isdefined(Main, :Base) && isdefined(Main.Base, :promote) &&
length(sv.src.slottypes) > 0 && sv.src.slottypes[1] typeof(getfield(Main.Base, :promote))
# check for non-isbits Tuple return
if sv.bestguess Tuple && !isbits(widenconst(sv.bestguess))
# See if inlining this call would change the enclosing function
# from inlineable to not inlineable.
# This heuristic is applied to functions that return non-bits
# tuples, since we want to be able to inline those functions to
# avoid the tuple allocation.
current_stmts = vcat(sv.src.code, pending_stmts)
if inline_worthy_stmts(current_stmts)
append!(current_stmts, ast)
if !inline_worthy_stmts(current_stmts)
return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited,
invoke_data)
end
end
end
end
# create the backedge
if isa(frame, InferenceState) && !frame.inferred && frame.cached
# in this case, the actual backedge linfo hasn't been computed
# yet, but will be when inference on the frame finishes
add_backedge!(frame, sv, 0)
else
add_backedge!(linfo, sv)
end
spvals = Any[]
for i = 1:length(methsp)
push!(spvals, methsp[i])
end
for i = 1:length(spvals)
si = spvals[i]
if isa(si, Symbol) || isa(si, SSAValue) || isa(si, Slot)
spvals[i] = QuoteNode(si)
end
end
nm = length(unwrap_unionall(metharg).parameters)
body = Expr(:block)
body.args = ast
propagate_inbounds = src.propagate_inbounds
# see if each argument occurs only once in the body expression
stmts = []
prelude_stmts = []
stmts_free = true # true = all entries of stmts are effect_free
for i = na:-1:1 # stmts_free needs to be calculated in reverse-argument order
#args_i = args[i]
aei = argexprs[i]
aeitype = argtype = widenconst(exprtype(aei, sv.src, sv.mod))
if i == 1 && !(invoke_texpr === nothing)
unshift!(prelude_stmts, invoke_texpr)
end
# ok for argument to occur more than once if the actual argument
# is a symbol or constant, or is not affected by previous statements
# that will exist after the inlining pass finishes
affect_free = stmts_free # false = previous statements might affect the result of evaluating argument
occ = 0
for j = length(body.args):-1:1
b = body.args[j]
if occ < 6
occ += occurs_more(b, x->(isa(x, Slot) && slot_id(x) == i), 6)
end
if occ > 0 && affect_free && !effect_free(b, src, method.module, true)
#TODO: we might be able to short-circuit this test better by memoizing effect_free(b) in the for loop over i
affect_free = false
end
if occ > 5 && !affect_free
break
end
end
free = effect_free(aei, sv.src, sv.mod, true)
if ((occ==0 && aeitype===Bottom) || (occ > 1 && !inline_worthy(aei, occ*2000)) ||
(affect_free && !free) || (!affect_free && !effect_free(aei, sv.src, sv.mod, false)))
if occ != 0
vnew = newvar!(sv, aeitype)
argexprs[i] = vnew
unshift!(prelude_stmts, Expr(:(=), vnew, aei))
stmts_free &= free
elseif !free && !isType(aeitype)
unshift!(prelude_stmts, aei)
stmts_free = false
end
end
end
invoke_fexpr === nothing || unshift!(prelude_stmts, invoke_fexpr)
# re-number the SSAValues and copy their type-info to the new ast
ssavalue_types = src.ssavaluetypes
if !isempty(ssavalue_types)
incr = length(sv.src.ssavaluetypes)
if incr != 0
body = ssavalue_increment(body, incr)
end
append!(sv.src.ssavaluetypes, ssavalue_types)
end
# ok, substitute argument expressions for argument names in the body
body = substitute!(body, na, argexprs, method.sig, spvals, length(sv.src.slotnames) - na)
append!(sv.src.slotnames, src.slotnames[(na + 1):end])
append!(sv.src.slottypes, src.slottypes[(na + 1):end])
append!(sv.src.slotflags, src.slotflags[(na + 1):end])
# make labels / goto statements unique
# relocate inlining information
newlabels = zeros(Int,label_counter(body.args)+1)
for i = 1:length(body.args)
a = body.args[i]
if isa(a,LabelNode)
a = a::LabelNode
newlabel = genlabel(sv)
newlabels[a.label+1] = newlabel.label
body.args[i] = newlabel
end
end
for i = 1:length(body.args)
a = body.args[i]
if isa(a,GotoNode)
a = a::GotoNode
body.args[i] = GotoNode(newlabels[a.label+1])
elseif isa(a,Expr)
a = a::Expr
if a.head === :enter
a.args[1] = newlabels[a.args[1]+1]
elseif a.head === :gotoifnot
a.args[2] = newlabels[a.args[2]+1]
end
end
end
# convert return statements into a series of goto's
retstmt = genlabel(sv)
local retval
multiret = false
lastexpr = pop!(body.args)
if isa(lastexpr,LabelNode)
push!(body.args, lastexpr)
push!(body.args, Expr(:call, GlobalRef(topmod, :error), "fatal error in type inference (lowering)"))
lastexpr = nothing
elseif !(isa(lastexpr,Expr) && lastexpr.head === :return)
# code sometimes ends with a meta node, e.g. inbounds pop
push!(body.args, lastexpr)
lastexpr = nothing
end
for a in body.args
push!(stmts, a)
if isa(a,Expr)
a = a::Expr
if a.head === :return
if !multiret
# create slot first time
retval = add_slot!(sv.src, rettype, false)
end
multiret = true
unshift!(a.args, retval)
a.head = :(=)
push!(stmts, GotoNode(retstmt.label))
end
end
end
if multiret
if lastexpr !== nothing
unshift!(lastexpr.args, retval)
lastexpr.head = :(=)
push!(stmts, lastexpr)
end
push!(stmts, retstmt)
expr = retval
else
# Dead code elimination can leave a non-return statement at the end
if lastexpr === nothing
expr = nothing
else
expr = lastexpr.args[1]
end
end
inlining_ignore = function (stmt::ANY)
isa(stmt, Expr) && return is_meta_expr(stmt::Expr)
isa(stmt, LineNumberNode) && return true
stmt === nothing && return true
return false
end
do_coverage = coverage_enabled()
if do_coverage
line = method.line
if !isempty(stmts) && isa(stmts[1], LineNumberNode)
line = (shift!(stmts)::LineNumberNode).line
end
# Check if we are switching module, which is necessary to catch user
# code inlined into `Base` with `--code-coverage=user`.
# Assume we are inlining directly into `enclosing` instead of another
# function inlined in it
mod = method.module
if mod === sv.mod
unshift!(stmts, Expr(:meta, :push_loc, method.file,
method.name, line))
else
unshift!(stmts, Expr(:meta, :push_loc, method.file,
method.name, line, mod))
end
push!(stmts, Expr(:meta, :pop_loc))
elseif !isempty(stmts)
if all(inlining_ignore, stmts)
empty!(stmts)
else
line::Int = method.line
if isa(stmts[1], LineNumberNode)
line = (shift!(stmts)::LineNumberNode).line
end
unshift!(stmts, Expr(:meta, :push_loc, method.file,
method.name, line))
if isa(stmts[end], LineNumberNode)
stmts[end] = Expr(:meta, :pop_loc)
else
push!(stmts, Expr(:meta, :pop_loc))
end
end
end
if !isempty(stmts) && !propagate_inbounds
# avoid redundant inbounds annotations
s_1, s_end = stmts[1], stmts[end]
i = 2
while length(stmts) > i && ((isa(s_1,Expr)&&s_1.head===:line) || isa(s_1,LineNumberNode))
s_1 = stmts[i]
i += 1
end
if isa(s_1, Expr) && s_1.head === :inbounds && s_1.args[1] === false &&
isa(s_end, Expr) && s_end.head === :inbounds && s_end.args[1] === :pop
else
# inlined statements are out-of-bounds by default
unshift!(stmts, Expr(:inbounds, false))
push!(stmts, Expr(:inbounds, :pop))
end
end
if isa(expr,Expr)
old_t = e.typ
if old_t expr.typ
# if we had better type information than the content being inlined,
# change the return type now to use the better type
expr.typ = old_t
end
end
if !isempty(prelude_stmts)
stmts = append!(prelude_stmts, stmts)
end
return (expr, stmts)
end
inline_worthy(body::ANY, cost::Integer) = true
# should the expression be part of the inline cost model
function inline_ignore(ex::ANY)
if isa(ex, LineNumberNode) || ex === nothing
return true
end
return isa(ex, Expr) && is_meta_expr(ex::Expr)
end
function inline_worthy_stmts(stmts::Vector{Any}, cost::Integer = 1000)
body = Expr(:block)
body.args = stmts
return inline_worthy(body, cost)
end
function inline_worthy(body::Expr, cost::Integer=1000) # precondition: 0 < cost; nominal cost = 1000
symlim = 1000 + 5_000_000 ÷ cost
nstmt = 0
for stmt in body.args
if !(isa(stmt, SSAValue) || inline_ignore(stmt))
nstmt += 1
end
end
if nstmt < (symlim + 500) ÷ 1000
symlim *= 16
symlim ÷= 1000
if occurs_more(body, e->!inline_ignore(e), symlim) < symlim
return true
end
end
return false
end
ssavalue_increment(body::ANY, incr) = body
ssavalue_increment(body::SSAValue, incr) = SSAValue(body.id + incr)
function ssavalue_increment(body::Expr, incr)
if is_meta_expr(body)
return body
end
for i in 1:length(body.args)
body.args[i] = ssavalue_increment(body.args[i], incr)
end
return body
end
const top_getfield = GlobalRef(Core, :getfield)
const top_tuple = GlobalRef(Core, :tuple)
function mk_getfield(texpr, i, T)
e = Expr(:call, top_getfield, texpr, i)
e.typ = T
return e
end
function mk_tuplecall(args, sv::InferenceState)
e = Expr(:call, top_tuple, args...)
e.typ = tuple_tfunc(Tuple{Any[widenconst(exprtype(x, sv.src, sv.mod)) for x in args]...})
return e
end
function inlining_pass!(sv::InferenceState)
eargs = sv.src.code
i = 1
stmtbuf = []
while i <= length(eargs)
ei = eargs[i]
if isa(ei, Expr)
eargs[i] = inlining_pass(ei, sv, stmtbuf, 1)
if !isempty(stmtbuf)
splice!(eargs, i:i-1, stmtbuf)
i += length(stmtbuf)
empty!(stmtbuf)
end
end
i += 1
end
end
const corenumtype = Union{Int32, Int64, Float32, Float64}
# return inlined replacement for `e`, inserting new needed statements
# at index `ins` in `stmts`.
function inlining_pass(e::Expr, sv::InferenceState, stmts, ins)
if e.head === :method
# avoid running the inlining pass on function definitions
return e
end
eargs = e.args
if length(eargs) < 1
return e
end
arg1 = eargs[1]
isccall = false
i0 = 1
# don't inline first (global) arguments of ccall, as this needs to be evaluated
# by the interpreter and inlining might put in something it can't handle,
# like another ccall (or try to move the variables out into the function)
if e.head === :foreigncall
# 3 is rewritten to 1 below to handle the callee.
i0 = 3
isccall = true
elseif is_known_call(e, Core.Intrinsics.llvmcall, sv.src, sv.mod)
i0 = 5
end
has_stmts = false # needed to preserve order-of-execution
prev_stmts_length = length(stmts)
for _i = length(eargs):-1:i0
if isccall && _i == 3
i = 1
isccallee = true
else
i = _i
isccallee = false
end
ei = eargs[i]
if isa(ei,Expr)
ei = ei::Expr
if ei.head === :&
argloc = ei.args
i = 1
ei = argloc[1]
if !isa(ei,Expr)
continue
end
ei = ei::Expr
else
argloc = eargs
end
sl0 = length(stmts)
res = inlining_pass(ei, sv, stmts, ins)
ns = length(stmts) - sl0 # number of new statements just added
if isccallee
restype = exprtype(res, sv.src, sv.mod)
if isa(restype, Const)
argloc[i] = restype.val
if !effect_free(res, sv.src, sv.mod, false)
insert!(stmts, ins+ns, res)
end
# Assume this is the last argument to process
break
end
end
if has_stmts && !effect_free(res, sv.src, sv.mod, false)
restype = exprtype(res, sv.src, sv.mod)
vnew = newvar!(sv, restype)
argloc[i] = vnew
insert!(stmts, ins+ns, Expr(:(=), vnew, res))
else
argloc[i] = res
end
if !has_stmts && ns > 0 && !(_i == i0)
for s = ins:ins+ns-1
stmt = stmts[s]
if !effect_free(stmt, sv.src, sv.mod, true)
has_stmts = true; break
end
end
end
end
end
if isccall
le = length(eargs)
for i = 4:2:(le - 1)
if eargs[i] === eargs[i + 1]
eargs[i + 1] = 0
end
end
end
if e.head !== :call
return e
end
ft = exprtype(arg1, sv.src, sv.mod)
if isa(ft, Const)
f = ft.val
elseif isa(ft, Conditional)
f = nothing
ft = Bool
else
f = nothing
if !( isleaftype(ft) || ft<:Type )
return e
end
end
ins += (length(stmts) - prev_stmts_length)
if sv.params.inlining
if isdefined(Main, :Base) &&
((isdefined(Main.Base, :^) && f === Main.Base.:^) ||
(isdefined(Main.Base, :.^) && f === Main.Base.:.^)) &&
length(e.args) == 3
a2 = e.args[3]
if isa(a2, Symbol) || isa(a2, Slot) || isa(a2, SSAValue)
ta2 = exprtype(a2, sv.src, sv.mod)
if isa(ta2, Const)
a2 = ta2.val
end
end
square = (a2 === Int32(2) || a2 === Int64(2))
triple = (a2 === Int32(3) || a2 === Int64(3))
if square || triple
a1 = e.args[2]
basenumtype = Union{corenumtype, Main.Base.Complex64, Main.Base.Complex128, Main.Base.Rational}
if isa(a1, basenumtype) || ((isa(a1, Symbol) || isa(a1, Slot) || isa(a1, SSAValue)) &&
exprtype(a1, sv.src, sv.mod) basenumtype)
if square
e.args = Any[GlobalRef(Main.Base,:*), a1, a1]
res = inlining_pass(e, sv, stmts, ins)
else
e.args = Any[GlobalRef(Main.Base,:*), Expr(:call, GlobalRef(Main.Base,:*), a1, a1), a1]
e.args[2].typ = e.typ
res = inlining_pass(e, sv, stmts, ins)
end
return res
end
end
end
end
for ninline = 1:100
ata = Vector{Any}(length(e.args))
ata[1] = ft
for i = 2:length(e.args)
a = exprtype(e.args[i], sv.src, sv.mod)
(a === Bottom || isvarargtype(a)) && return e
ata[i] = a
end
res = inlineable(f, ft, e, ata, sv, stmts)
if isa(res,Tuple)
if isa(res[2],Array) && !isempty(res[2])
splice!(stmts, ins:ins-1, res[2])
ins += length(res[2])
end
res = res[1]
end
if res !== NF
# iteratively inline apply(f, tuple(...), tuple(...), ...) in order
# to simplify long vararg lists as in multi-arg +
if isa(res,Expr) && is_known_call(res, _apply, sv.src, sv.mod)
e = res::Expr
f = _apply; ft = abstract_eval_constant(f)
else
return res
end
end
if f === _apply
na = length(e.args)
newargs = Vector{Any}(na-2)
newstmts = Any[]
effect_free_upto = 0
for i = 3:na
aarg = e.args[i]
argt = exprtype(aarg, sv.src, sv.mod)
t = widenconst(argt)
if isa(aarg,Expr) && (is_known_call(aarg, tuple, sv.src, sv.mod) || is_known_call(aarg, svec, sv.src, sv.mod))
# apply(f,tuple(x,y,...)) => f(x,y,...)
newargs[i-2] = aarg.args[2:end]
elseif isa(argt,Const) && (isa(argt.val, Tuple) || isa(argt.val, SimpleVector)) &&
effect_free(aarg, sv.src, sv.mod, true)
newargs[i-2] = Any[ QuoteNode(x) for x in argt.val ]
elseif isa(aarg, Tuple) || (isa(aarg, QuoteNode) && (isa(aarg.value, Tuple) || isa(aarg.value, SimpleVector)))
if isa(aarg, QuoteNode)
aarg = aarg.value
end
newargs[i-2] = Any[ QuoteNode(x) for x in aarg ]
elseif isa(t, DataType) && t.name === Tuple.name && !isvatuple(t) &&
length(t.parameters) <= sv.params.MAX_TUPLE_SPLAT
for k = (effect_free_upto+1):(i-3)
as = newargs[k]
for kk = 1:length(as)
ak = as[kk]
if !effect_free(ak, sv.src, sv.mod, true)
tmpv = newvar!(sv, widenconst(exprtype(ak, sv.src, sv.mod)))
push!(newstmts, Expr(:(=), tmpv, ak))
as[kk] = tmpv
end
end
end
effect_free_upto = i-3
if effect_free(aarg, sv.src, sv.mod, true)
# apply(f,t::(x,y)) => f(t[1],t[2])
tmpv = aarg
else
# apply(f,t::(x,y)) => tmp=t; f(tmp[1],tmp[2])
tmpv = newvar!(sv, t)
push!(newstmts, Expr(:(=), tmpv, aarg))
end
tp = t.parameters
newargs[i-2] = Any[ mk_getfield(tmpv,j,tp[j]) for j=1:length(tp) ]
else
# not all args expandable
return e
end
end
splice!(stmts, ins:ins-1, newstmts)
ins += length(newstmts)
e.args = [Any[e.args[2]]; newargs...]
# now try to inline the simplified call
ft = exprtype(e.args[1], sv.src, sv.mod)
if isa(ft, Const)
f = ft.val
elseif isa(ft, Conditional)
f = nothing
ft = Bool
else
f = nothing
if !( isleaftype(ft) || ft<:Type )
return e
end
end
else
return e
end
end
return e
end
const compiler_temp_sym = Symbol("#temp#")
function add_slot!(src::CodeInfo, typ::ANY, is_sa::Bool, name::Symbol=compiler_temp_sym)
@assert !isa(typ, Const) && !isa(typ, Conditional)
id = length(src.slotnames) + 1
push!(src.slotnames, name)
push!(src.slottypes, typ)
push!(src.slotflags, Slot_Assigned + is_sa * Slot_AssignedOnce)
return SlotNumber(id)
end
function is_known_call(e::Expr, func::ANY, src::CodeInfo, mod::Module)
if e.head !== :call
return false
end
f = exprtype(e.args[1], src, mod)
return isa(f, Const) && f.val === func
end
function is_known_call_p(e::Expr, pred::ANY, src::CodeInfo, mod::Module)
if e.head !== :call
return false
end
f = exprtype(e.args[1], src, mod)
return (isa(f, Const) && pred(f.val)) || (isType(f) && pred(f.parameters[1]))
end
function record_used(e::ANY, T::ANY, used::Vector{Bool})
if isa(e,T)
used[e.id+1] = true
elseif isa(e,Expr)
i0 = e.head === :(=) ? 2 : 1
for i = i0:length(e.args)
record_used(e.args[i], T, used)
end
end
end
function remove_unused_vars!(src::CodeInfo)
used = fill(false, length(src.slotnames)+1)
used_ssa = fill(false, length(src.ssavaluetypes)+1)
for i = 1:length(src.code)
record_used(src.code[i], Slot, used)
record_used(src.code[i], SSAValue, used_ssa)
end
for i = 1:length(src.code)
e = src.code[i]
if isa(e,NewvarNode) && !used[e.slot.id+1]
src.code[i] = nothing
elseif isa(e,Expr) && e.head === :(=)
if (isa(e.args[1],Slot) && !used[e.args[1].id+1]) ||
(isa(e.args[1],SSAValue) && !used_ssa[e.args[1].id+1])
src.code[i] = e.args[2]
end
end
end
end
function delete_vars!(src::CodeInfo, r::ObjectIdDict)
filter!(x->!(isa(x,Expr) && (x.head === :(=) || x.head === :const) &&
haskey(r, normvar(x.args[1]))),
src.code)
return src
end
function replace_vars!(src::CodeInfo, r::ObjectIdDict)
for i = 1:length(src.code)
src.code[i] = _replace_vars!(src.code[i], r)
end
return src
end
function _replace_vars!(e::ANY, r::ObjectIdDict)
if isa(e,SSAValue) || isa(e,Slot)
v = normvar(e)
if haskey(r, v)
return r[v]
end
end
if isa(e,Expr)
for i = 1:length(e.args)
e.args[i] = _replace_vars!(e.args[i], r)
end
end
return e
end
is_argument(nargs::Int, v::Slot) = slot_id(v) <= nargs
normslot(s::SlotNumber) = s
normslot(s::TypedSlot) = SlotNumber(slot_id(s))
normvar(s::Slot) = normslot(s)
normvar(s::SSAValue) = s
normvar(s::ANY) = s
# given a single-assigned var and its initializer `init`, return what we can
# replace `var` with, or `var` itself if we shouldn't replace it
function get_replacement(table, var::Union{SlotNumber, SSAValue}, init::ANY, nargs, slottypes, ssavaluetypes)
#if isa(init, QuoteNode) # this can cause slight code size increases
# return init
if (isa(init, Expr) && init.head === :static_parameter) || isa(init, corenumtype) ||
init === () || init === nothing
return init
elseif isa(init, Slot) && is_argument(nargs, init::Slot)
# the transformation is not ideal if the assignment
# is present for the auto-unbox functionality
# (from inlining improved type inference information)
# and this transformation would worsen the type information
# everywhere later in the function
ityp = isa(init, TypedSlot) ? init.typ : slottypes[(init::SlotNumber).id]
if ityp (isa(var,SSAValue) ? ssavaluetypes[var.id + 1] : slottypes[var.id])
return init
end
elseif isa(init, SSAValue)
if isa(var, SlotNumber) && slottypes[var.id] Tuple
# Here we avoid replacing a Slot with an SSAValue when the type is an
# aggregate. That can cause LLVM to generate a bunch of extra memcpys
# if the data ever needs to be stack allocated later.
return var
end
if haskey(table, init)
return get_replacement(table, init, table[init], nargs, slottypes, ssavaluetypes)
end
return init
elseif isa(init, SlotNumber) && haskey(table, init)
return get_replacement(table, init, table[init], nargs, slottypes, ssavaluetypes)
elseif isa(init, TypedSlot)
sl = normslot(init)
if haskey(table, sl)
rep = get_replacement(table, sl, table[sl], nargs, slottypes, ssavaluetypes)
if isa(rep, SlotNumber)
rep = TypedSlot(rep.id, init.typ)
end
return rep
end
end
return var
end
# remove all single-assigned vars v in "v = x" where x is an argument.
# "sa" is the result of find_sa_vars
function remove_redundant_temp_vars!(src::CodeInfo, nargs::Int, sa::ObjectIdDict)
flags = src.slotflags
slottypes = src.slottypes
ssavaluetypes = src.ssavaluetypes
repls = ObjectIdDict()
for (v, init) in sa
repl = get_replacement(sa, v, init, nargs, slottypes, ssavaluetypes)
compare = isa(repl,TypedSlot) ? normslot(repl) : repl
if compare !== v
repls[v] = repl
end
end
if !isempty(repls)
delete_vars!(src, repls)
replace_vars!(src, repls)
end
return src
end
# compute set of slots assigned once
function find_sa_vars(src::CodeInfo, nargs::Int)
body = src.code
av = ObjectIdDict()
av2 = ObjectIdDict()
for i = 1:length(body)
e = body[i]
if isa(e,Expr) && e.head === :(=)
lhs = e.args[1]
if isa(lhs, SSAValue)
av[lhs] = e.args[2]
elseif isa(lhs, Slot)
lhs = normslot(lhs)
id = lhs.id
# exclude args and used undef vars
# this transformation is not valid for vars used before def.
# we need to preserve the point of assignment to know where to
# throw errors (issue #4645).
if id > nargs && (src.slotflags[id] & Slot_UsedUndef == 0)
if !haskey(av, lhs)
av[lhs] = e.args[2]
else
av2[lhs] = true
end
end
end
end
end
filter!((v, _) -> !haskey(av2, v), av)
return av
end
symequal(x::SSAValue, y::SSAValue) = x.id === y.id
symequal(x::Slot , y::Slot) = x.id === y.id
symequal(x::ANY , y::ANY) = x === y
function occurs_outside_getfield(e::ANY, sym::ANY,
sv::InferenceState, field_count::Int, field_names::ANY)
if e === sym || (isa(e, Slot) && isa(sym, Slot) && slot_id(e) == slot_id(sym))
return true
end
if isa(e,Expr)
e = e::Expr
head = e.head
is_meta_expr_head(head) && return false
if is_known_call(e, getfield, sv.src, sv.mod) && symequal(e.args[2],sym)
idx = e.args[3]
if isa(idx,QuoteNode) && (idx.value in field_names)
return false
end
if isa(idx,Int) && (1 <= idx <= field_count)
return false
end
return true
end
if head === :(=)
return occurs_outside_getfield(e.args[2], sym, sv,
field_count, field_names)
else
if (head === :block && isa(sym, Slot) &&
sv.src.slotflags[slot_id(sym)] & Slot_UsedUndef == 0)
ignore_void = true
else
ignore_void = false
end
for a in e.args
if ignore_void && isa(a, Slot) && slot_id(a) == slot_id(sym)
continue
end
if occurs_outside_getfield(a, sym, sv, field_count, field_names)
return true
end
end
end
end
return false
end
function void_use_elim_pass!(sv::InferenceState)
# Remove top level SSAValue and slots that is `!usedUndef`.
# Also remove some `nothing` while we are at it....
not_void_use = function (ex::ANY)
if isa(ex, SSAValue)
# Explicitly listed here for clarity
return false
elseif isa(ex, Slot)
return sv.src.slotflags[slot_id(ex)] & Slot_UsedUndef != 0
elseif isa(ex, GlobalRef)
ex = ex::GlobalRef
return !isdefined(ex.mod, ex.name)
elseif isa(ex, Expr)
h = ex.head
if h === :return || h === :(=) || h === :gotoifnot || is_meta_expr_head(h)
return true
end
return !effect_free(ex, sv.src, sv.mod, false)
elseif (isa(ex, GotoNode) || isa(ex, LineNumberNode) ||
isa(ex, NewvarNode) || isa(ex, Symbol) || isa(ex, LabelNode))
# This is a list of special types handled by the compiler
return true
end
return false
end
filter!(not_void_use, sv.src.code::Array{Any,1})
nothing
end
function meta_elim_pass!(code::Array{Any,1}, propagate_inbounds::Bool, do_coverage::Bool)
# 1. Remove place holders
#
# 2. If coverage is off, remove line number nodes that don't mark any
# real expressions.
#
# 3. Remove top level SSAValue
#
# 4. Handle bounds check elision
#
# 4.1. If check_bounds is always on, delete all `Expr(:boundscheck)`
# 4.2. If check_bounds is always off, delete all boundscheck blocks.
# 4.3. If check_bounds is default, figure out whether each checkbounds
# blocks needs to be eliminated or could be eliminated when inlined
# into another function. Delete the blocks that should be eliminated
# and delete the `Expr(:boundscheck)` for blocks that will never be
# deleted. (i.e. the ones that are not eliminated with
# `length(inbounds_stack) >= 2`)
#
# When deleting IR with boundscheck, keep the label node in order to not
# confuse later passes or codegen. (we could also track if any SSAValue
# is deleted while still having uses that are not but that's a little
# expensive).
#
# 5. Clean up `Expr(:inbounds)`
#
# Delete all `Expr(:inbounds)` that is unnecessary, which is all of them
# for non-default check_bounds. For default check_bounds this includes
#
# * `Expr(:inbounds, true)` in `Expr(:inbounds, true)`
# * `Expr(:inbounds, false)` when
# `!is_inbounds && length(inbounds_stack) >= 2`
#
# Functions without `propagate_inbounds` have an implicit `false` on the
# `inbounds_stack`
#
# There are other cases in which we can eliminate `Expr(:inbounds)` or
# `Expr(:boundscheck)` (e.g. when they don't enclose any non-meta
# expressions). Those are a little harder to detect and are hopefully
# not too common.
check_bounds = JLOptions().check_bounds
inbounds_stack = propagate_inbounds ? Bool[] : Bool[false]
# Whether the push is deleted (therefore if the pop has to be too)
# Shared for `Expr(:boundscheck)` and `Expr(:inbounds)`
bounds_elim_stack = Bool[]
# The expression index of the push, set to `0` when encountering a
# non-meta expression that might be affect by the push.
# The clearing needs to be propagated up during pop
# This is not pushed to if the push is already eliminated
# Also shared for `Expr(:boundscheck)` and `Expr(:inbounds)`
bounds_push_pos_stack = Int[0] # always non-empty
# Number of boundscheck pushes in a eliminated boundscheck block
void_boundscheck_depth = 0
is_inbounds = check_bounds == 2
enabled = true
# Position of the last line number node without any non-meta expressions
# in between.
prev_dbg_stack = Int[0] # always non-empty
# Whether there's any non-meta exprs after the enclosing `push_loc`
push_loc_pos_stack = Int[0] # always non-empty
for i in 1:length(code)
ex = code[i]
if ex === nothing
continue
elseif isa(ex, SSAValue)
code[i] = nothing
continue
elseif isa(ex, LabelNode)
prev_dbg_stack[end] = 0
push_loc_pos_stack[end] = 0
continue
elseif !do_coverage && (isa(ex, LineNumberNode) ||
(isa(ex, Expr) && (ex::Expr).head === :line))
prev_label = prev_dbg_stack[end]
if prev_label != 0
code[prev_label] = nothing
end
prev_dbg_stack[end] = i
continue
elseif !isa(ex, Expr)
if enabled
prev_dbg_stack[end] = 0
push_loc_pos_stack[end] = 0
bounds_push_pos_stack[end] = 0
else
code[i] = nothing
end
continue
end
ex = ex::Expr
args = ex.args
head = ex.head
if head === :boundscheck
if !enabled
# we are in an eliminated boundscheck, simply record the number
# of push/pop
if !(args[1] === :pop)
void_boundscheck_depth += 1
elseif void_boundscheck_depth == 0
# There must have been a push
pop!(bounds_elim_stack)
enabled = true
else
void_boundscheck_depth -= 1
end
code[i] = nothing
elseif args[1] === :pop
# This will also delete pops that don't match
if (isempty(bounds_elim_stack) ? true :
pop!(bounds_elim_stack))
code[i] = nothing
continue
end
push_idx = bounds_push_pos_stack[end]
if length(bounds_push_pos_stack) > 1
pop!(bounds_push_pos_stack)
end
if push_idx > 0
code[push_idx] = nothing
code[i] = nothing
else
bounds_push_pos_stack[end] = 0
end
elseif is_inbounds
code[i] = nothing
push!(bounds_elim_stack, true)
enabled = false
elseif check_bounds == 1 || length(inbounds_stack) >= 2
# Not inbounds and at least two levels deep, this will never
# be eliminated when inlined to another function.
code[i] = nothing
push!(bounds_elim_stack, true)
else
push!(bounds_elim_stack, false)
push!(bounds_push_pos_stack, i)
end
continue
end
if !enabled && !(do_coverage && head === :meta)
code[i] = nothing
continue
end
if head === :inbounds
if check_bounds != 0
code[i] = nothing
continue
end
arg1 = args[1]
if arg1 === true
if !isempty(inbounds_stack) && inbounds_stack[end]
code[i] = nothing
push!(bounds_elim_stack, true)
else
is_inbounds = true
push!(bounds_elim_stack, false)
push!(bounds_push_pos_stack, i)
end
push!(inbounds_stack, true)
elseif arg1 === false
if is_inbounds
# There must have been a `true` on the stack so
# `inbounds_stack` must not be empty
if !inbounds_stack[end]
is_inbounds = false
end
push!(bounds_elim_stack, false)
push!(bounds_push_pos_stack, i)
elseif length(inbounds_stack) >= 2
code[i] = nothing
push!(bounds_elim_stack, true)
else
push!(bounds_elim_stack, false)
push!(bounds_push_pos_stack, i)
end
push!(inbounds_stack, false)
else
# pop
inbounds_len = length(inbounds_stack)
if inbounds_len != 0
pop!(inbounds_stack)
inbounds_len -= 1
end
# This will also delete pops that don't match
if (isempty(bounds_elim_stack) ? true :
pop!(bounds_elim_stack))
# No need to update `is_inbounds` since the push was a no-op
code[i] = nothing
continue
end
if inbounds_len >= 2
is_inbounds = (inbounds_stack[inbounds_len] ||
inbounds_stack[inbounds_len - 1])
elseif inbounds_len == 1
is_inbounds = inbounds_stack[inbounds_len]
else
is_inbounds = false
end
push_idx = bounds_push_pos_stack[end]
if length(bounds_push_pos_stack) > 1
pop!(bounds_push_pos_stack)
end
if push_idx > 0
code[push_idx] = nothing
code[i] = nothing
else
bounds_push_pos_stack[end] = 0
end
end
continue
end
if head !== :meta
prev_dbg_stack[end] = 0
push_loc_pos_stack[end] = 0
bounds_push_pos_stack[end] = 0
continue
end
nargs = length(args)
if do_coverage || nargs == 0
continue
end
arg1 = args[1]
if arg1 === :push_loc
push!(prev_dbg_stack, 0)
push!(push_loc_pos_stack, i)
elseif arg1 === :pop_loc
prev_dbg = if length(prev_dbg_stack) > 1
pop!(prev_dbg_stack)
else
prev_dbg_stack[end]
end
if prev_dbg > 0
code[prev_dbg] = nothing
end
push_loc = if length(push_loc_pos_stack) > 1
pop!(push_loc_pos_stack)
else
push_loc_pos_stack[end]
end
if push_loc > 0
code[push_loc] = nothing
code[i] = nothing
else
prev_dbg_stack[end] = 0
push_loc_pos_stack[end] = 0
end
else
continue
end
end
return filter!(x -> x !== nothing, code)
end
# does the same job as alloc_elim_pass for allocations inline in getfields
# TODO can probably be removed when we switch to a linear IR
function getfield_elim_pass!(sv::InferenceState)
body = sv.src.code
for i = 1:length(body)
body[i] = _getfield_elim_pass!(body[i], sv)
end
end
function _getfield_elim_pass!(e::Expr, sv::InferenceState)
for i = 1:length(e.args)
e.args[i] = _getfield_elim_pass!(e.args[i], sv)
end
if is_known_call(e, getfield, sv.src, sv.mod) && length(e.args)==3 &&
(isa(e.args[3],Int) || isa(e.args[3],QuoteNode))
e1 = e.args[2]
j = e.args[3]
if isa(e1,Expr)
alloc = is_allocation(e1, sv)
if alloc !== false
flen, fnames = alloc
if isa(j,QuoteNode)
j = findfirst(fnames, j.value)
end
if 1 <= j <= flen
ok = true
for k = 2:length(e1.args)
k == j+1 && continue
if !effect_free(e1.args[k], sv.src, sv.mod, true)
ok = false; break
end
end
if ok
return e1.args[j+1]
end
end
end
elseif isa(e1, GlobalRef) || isa(e1, Symbol) || isa(e1, Slot) || isa(e1, SSAValue)
# non-self-quoting value
else
if isa(e1, QuoteNode)
e1 = e1.value
end
if isimmutable(e1) || isa(e1,SimpleVector)
# SimpleVector length field is immutable
if isa(j, QuoteNode)
j = j.value
if !(isa(j,Int) || isa(j,Symbol))
return e
end
end
if isdefined(e1, j)
e1j = getfield(e1, j)
if !is_self_quoting(e1j)
e1j = QuoteNode(e1j)
end
return e1j
end
end
end
end
return e
end
_getfield_elim_pass!(e::ANY, sv) = e
# check if e is a successful allocation of an struct
# if it is, returns (n,f) such that it is always valid to call
# getfield(..., 1 <= x <= n) or getfield(..., x in f) on the result
function is_allocation(e::ANY, sv::InferenceState)
isa(e, Expr) || return false
if is_known_call(e, tuple, sv.src, sv.mod)
return (length(e.args)-1,())
elseif e.head === :new
typ = widenconst(exprtype(e, sv.src, sv.mod))
if isa(typ, DataType) && isleaftype(typ)
nf = length(e.args) - 1
names = fieldnames(typ)
@assert(nf <= nfields(typ))
if nf < nfields(typ)
# some fields were left undef
# we could potentially propagate Bottom
# for pointer fields
names = names[1:nf]
end
return (nf, names)
end
end
false
end
# Replace branches with constant conditions with unconditional branches
function gotoifnot_elim_pass!(sv::InferenceState)
body = sv.src.code
i = 1
while i < length(body)
expr = body[i]
i += 1
isa(expr, Expr) || continue
expr = expr::Expr
expr.head === :gotoifnot || continue
cond = expr.args[1]
condt = exprtype(cond, sv.src, sv.mod)
isa(condt, Const) || continue
val = (condt::Const).val
# Codegen should emit an unreachable if val is not a Bool so
# we don't need to do anything (also, type inference currently
# doesn't recognize the error for strictly non-Bool condition)
if isa(val, Bool)
# in case there's side effects... (like raising `UndefVarError`)
body[i - 1] = cond
if val === false
insert!(body, i, GotoNode(expr.args[2]))
i += 1
end
end
end
end
# eliminate allocation of unnecessary objects
# that are only used as arguments to safe getfield calls
function alloc_elim_pass!(sv::InferenceState)
body = sv.src.code
bexpr = Expr(:block)
bexpr.args = body
vs = find_sa_vars(sv.src, sv.nargs)
remove_redundant_temp_vars!(sv.src, sv.nargs, vs)
remove_unused_vars!(sv.src)
i = 1
while i < length(body)
e = body[i]
if !isa(e, Expr)
i += 1
continue
end
e = e::Expr
if e.head === :(=) && (isa(e.args[1], SSAValue) ||
(isa(e.args[1], Slot) && haskey(vs, normslot(e.args[1]))))
var = e.args[1]
rhs = e.args[2]
# Need to make sure LLVM can recognize this as LLVM ssa value too
is_ssa = (isa(var, SSAValue) ||
sv.src.slotflags[slot_id(var)] & Slot_UsedUndef == 0)
else
var = nothing
rhs = e
is_ssa = false # doesn't matter as long as it's a Bool...
end
alloc = is_allocation(rhs, sv)
if alloc !== false
nv, field_names = alloc
tup = rhs.args
# This makes sure the value doesn't escape so we can elide
# allocation of mutable structs too
if (var !== nothing &&
occurs_outside_getfield(bexpr, var, sv, nv, field_names))
i += 1
continue
end
deleteat!(body, i) # remove tuple allocation
# convert tuple allocation to a series of local var assignments
n_ins = 0
if var === nothing
for j=1:nv
tupelt = tup[j+1]
if !(isa(tupelt,Number) || isa(tupelt,AbstractString) ||
isa(tupelt,QuoteNode) || isa(tupelt, SSAValue))
insert!(body, i+n_ins, tupelt)
n_ins += 1
end
end
else
vals = Vector{Any}(nv)
local new_slots::Vector{Int}
if !is_ssa
new_slots = Vector{Int}(nv)
end
for j=1:nv
tupelt = tup[j+1]
# If `!is_ssa` we have to create new variables for each
# (used) fields in order to preserve the undef check.
if is_ssa && (isa(tupelt,Number) ||
isa(tupelt,AbstractString) ||
isa(tupelt,QuoteNode) || isa(tupelt, SSAValue))
vals[j] = tupelt
else
elty = exprtype(tupelt, sv.src, sv.mod)
if is_ssa
tmpv = newvar!(sv, elty)
else
tmpv = add_slot!(sv.src, widenconst(elty), false,
sv.src.slotnames[slot_id(var)])
tmpv_id = slot_id(tmpv)
new_slots[j] = tmpv_id
sv.src.slotflags[tmpv_id] |= Slot_UsedUndef
end
tmp = Expr(:(=), tmpv, tupelt)
insert!(body, i+n_ins, tmp)
vals[j] = tmpv
n_ins += 1
end
end
replace_getfield!(bexpr, var, vals, field_names, sv)
if !is_ssa
i += replace_newvar_node!(body, slot_id(var),
new_slots, i)
elseif isa(var, Slot)
# occurs_outside_getfield might have allowed
# void use of the slot, we need to delete them too
i -= delete_void_use!(body, var, i)
end
end
# Do not increment counter and do the optimization recursively
# on the allocation of fields too.
# This line can probably be added back for linear IR
# i += n_ins
else
i += 1
end
end
end
# Return the number of expressions added before `i0`
function replace_newvar_node!(body, orig, new_slots, i0)
nvars = length(new_slots)
nvars == 0 && return 0
narg = length(body)
i = 1
nins = 0
newvars = [ NewvarNode(SlotNumber(id)) for id in new_slots ]
while i <= narg
a = body[i]
if isa(a, NewvarNode) && slot_id((a::NewvarNode).slot) == orig
splice!(body, i, newvars)
if i - nins < i0
nins += nvars - 1
end
narg += nvars - 1
i += nvars
else
i += 1
end
end
return nins
end
# Return the number of expressions deleted before `i0`
function delete_void_use!(body, var::Slot, i0)
narg = length(body)
i = 1
ndel = 0
while i <= narg
a = body[i]
if isa(a, Slot) && slot_id(a) == slot_id(var)
deleteat!(body, i)
if i + ndel < i0
ndel += 1
end
narg -= 1
else
i += 1
end
end
return ndel
end
function replace_getfield!(e::Expr, tupname, vals, field_names, sv::InferenceState)
for i = 1:length(e.args)
a = e.args[i]
if isa(a,Expr) && is_known_call(a, getfield, sv.src, sv.mod) &&
symequal(a.args[2],tupname)
idx = if isa(a.args[3], Int)
a.args[3]
else
@assert isa(a.args[3], QuoteNode)
findfirst(field_names, a.args[3].value)
end
@assert(idx > 0) # clients should check that all getfields are valid
val = vals[idx]
# original expression might have better type info than
# the tuple element expression that's replacing it.
if isa(val, Slot)
val = val::Slot
id = slot_id(val)
valtyp = isa(val, TypedSlot) ? val.typ : sv.src.slottypes[id]
if a.typ valtyp && !(valtyp a.typ)
if isa(val, TypedSlot)
val = TypedSlot(id, a.typ)
end
sv.src.slottypes[id] = widenconst(a.typ)
end
elseif isa(val,SSAValue)
val = val::SSAValue
typ = exprtype(val, sv.src, sv.mod)
if a.typ typ && !(typ a.typ)
sv.src.ssavaluetypes[val.id + 1] = a.typ
end
end
e.args[i] = val
elseif isa(a, Expr)
replace_getfield!(a::Expr, tupname, vals, field_names, sv)
end
end
end
# fix label numbers to always equal the statement index of the label
function reindex_labels!(sv::InferenceState)
body = sv.src.code
mapping = zeros(Int, sv.label_counter)
for i = 1:length(body)
el = body[i]
if isa(el,LabelNode)
mapping[el.label] = i
body[i] = LabelNode(i)
end
end
for i = 1:length(body)
el = body[i]
# For goto and enter, the statement and the target has to be
# both reachable or both not. For gotoifnot, the dead code
# elimination in type_annotate! can delete the target
# of a reachable (but never taken) node. In which case we can
# just replace the node with the branch condition.
if isa(el,GotoNode)
labelnum = mapping[el.label]
@assert labelnum !== 0
body[i] = GotoNode(labelnum)
elseif isa(el,Expr)
el = el::Expr
if el.head === :gotoifnot
labelnum = mapping[el.args[2]]
if labelnum !== 0
el.args[2] = mapping[el.args[2]]
else
# Might have side effects
body[i] = el.args[1]
end
elseif el.head === :enter
labelnum = mapping[el.args[1]]
@assert labelnum !== 0
el.args[1] = labelnum
end
end
end
end
function return_type(f::ANY, t::ANY)
params = InferenceParams(ccall(:jl_get_tls_world_age, UInt, ()))
rt = Union{}
if isa(f, Builtin)
rt = builtin_tfunction(f, Any[t.parameters...], nothing, params)
if isa(rt, TypeVar)
rt = rt.ub
else
rt = widenconst(rt)
end
else
for m in _methods(f, t, -1, params.world)
ty = typeinf_type(m[3], m[1], m[2], true, params)
ty === nothing && return Any
rt = tmerge(rt, ty)
rt === Any && break
end
end
return rt
end
#### bootstrapping ####
# make sure that typeinf is executed before turning on typeinf_ext
# this ensures that typeinf_ext doesn't recurse before it can add the item to the workq
# especially try to make sure any recursive and leaf functions have concrete signatures,
# since we won't be able to specialize & infer them at runtime
let fs = Any[typeinf_ext, typeinf, typeinf_edge, occurs_outside_getfield, pure_eval_call],
world = ccall(:jl_get_world_counter, UInt, ())
for x in t_ffunc_val
push!(fs, x[3])
end
for i = 1:length(t_ifunc)
if isassigned(t_ifunc, i)
x = t_ifunc[i]
push!(fs, x[3])
else
println(STDERR, "WARNING: tfunc missing for ", reinterpret(IntrinsicFunction, Int32(i)))
end
end
for f in fs
for m in _methods_by_ftype(Tuple{typeof(f), Vararg{Any}}, 10, typemax(UInt))
# remove any TypeVars from the intersection
typ = Any[m[1].parameters...]
for i = 1:length(typ)
if isa(typ[i], TypeVar)
typ[i] = typ[i].ub
end
end
typeinf_type(m[3], Tuple{typ...}, m[2], true, InferenceParams(world))
end
end
end