5444 lines
191 KiB
Julia
5444 lines
191 KiB
Julia
# 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
|