Skip to content

Commit

Permalink
Merge pull request #16929 from JuliaLang/jb/linfocopying
Browse files Browse the repository at this point in the history
try to simplify information flow around inference
  • Loading branch information
vtjnash authored Jun 16, 2016
2 parents 7ad5eee + f57fd54 commit 4accf8a
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 153 deletions.
212 changes: 96 additions & 116 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ type InferenceState

# info on the state of inference and the linfo
linfo::LambdaInfo
destination::LambdaInfo # results need to be copied here when we finish
nargs::Int
stmt_types::Vector{Any}
# return type
Expand All @@ -73,11 +72,9 @@ type InferenceState
inworkq::Bool
optimize::Bool
inferred::Bool
tfunc_bp::Union{TypeMapEntry, Void}

function InferenceState(linfo::LambdaInfo, optimize::Bool)
@assert isa(linfo.code,Array{Any,1})
linfo.inInference = true
nslots = length(linfo.slotnames)
nl = label_counter(linfo.code)+1

Expand Down Expand Up @@ -156,12 +153,12 @@ type InferenceState
inmodule = isdefined(linfo, :def) ? linfo.def.module : current_module() # toplevel thunks are inferred in the current module
frame = new(
sp, nl, Dict{SSAValue, Bool}(), inmodule, 0, false,
linfo, linfo, la, s, Union{}, W, n,
linfo, la, s, Union{}, W, n,
cur_hand, handler_at, n_handlers,
ssavalue_uses, ssavalue_init,
ObjectIdDict(), #Dict{InferenceState, Vector{LineNum}}(),
Vector{Tuple{InferenceState, Vector{LineNum}}}(),
false, false, false, optimize, false, nothing)
false, false, false, optimize, false)
push!(active, frame)
nactive[] += 1
return frame
Expand Down Expand Up @@ -1387,25 +1384,25 @@ function newvar!(sv::InferenceState, typ)
end

# create a specialized LambdaInfo from a method
function specialize_method(method::Method, types::ANY, sp::SimpleVector)
li = ccall(:jl_get_specialized, Ref{LambdaInfo}, (Any, Any, Any), method, types, sp)
return li
function specialize_method(method::Method, types::ANY, sp::SimpleVector, cached)
if cached
return ccall(:jl_specializations_get_linfo, Ref{LambdaInfo}, (Any, Any, Any), method, types, sp)
else
return ccall(:jl_get_specialized, Ref{LambdaInfo}, (Any, Any, Any), method, types, sp)
end
end

# create copies of any field that type-inference might modify
function unshare_linfo!(li::LambdaInfo)
if !isa(li.code, Array{Any,1})
orig = li.def.lambda_template
if isa(li.code, Array{UInt8,1})
li.code = ccall(:jl_uncompress_ast, Any, (Any,Any), li, li.code)
else
li.code = copy_exprargs(li.code)
elseif li.code === orig.code
li.code = copy_exprargs(orig.code)
end
li.slotnames = copy(li.slotnames)
li.slotflags = copy(li.slotflags)
if isa(li.slottypes, Array)
li.slottypes = copy(li.slottypes)
end
if isa(li.ssavaluetypes, Array)
li.ssavaluetypes = copy(li.ssavaluetypes)
if !li.def.isstaged
li.slotnames = copy(li.slotnames)
li.slotflags = copy(li.slotflags)
end
return li
end
Expand All @@ -1414,9 +1411,11 @@ end
function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtree::Bool, optimize::Bool, cached::Bool, caller)
local code = nothing
local frame = nothing
# check cached specializations
# for an existing result stored there
if cached
if isa(caller, LambdaInfo)
code = caller
elseif cached
# check cached specializations
# for an existing result stored there
if !is(method.specializations, nothing)
code = ccall(:jl_specializations_lookup, Any, (Any, Any), method, atypes)
if isa(code, Void)
Expand All @@ -1436,89 +1435,80 @@ function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtr
code = nothing
end
end
end

if isa(code, LambdaInfo) && code.inInference
# inference on this signature may be in progress,
# find the corresponding frame in the active list
for infstate in active
infstate === nothing && continue
infstate = infstate::InferenceState
if code === infstate.linfo
frame = infstate
break
if caller === nothing && in_typeinf_loop
# if the caller needed the ast, but we are already in the typeinf loop
# then just return early -- we can't fulfill this request
# if the client was inlining, then this means we decided not to try to infer this
# particular signature (due to signature coarsening in abstract_call_gf_by_type)
# and attempting to force it now would be a bad idea (non terminating)
skip = true
if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.module == Main.Base)
# however, some gf have special tfunc and meaning they wouldn't have been inferred yet
# check the same conditions from abstract_call to detect this case
if method.name == :promote_type || method.name == :typejoin
skip = false
elseif method.name == :getindex || method.name == :next || method.name == :indexed_next
argtypes = atypes.parameters
if length(argtypes)>2 && argtypes[3] Int
at2 = widenconst(argtypes[2])
if (at2 <: Tuple ||
(isa(at2, DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) &&
(at2::DataType).name === Main.Base.Pair.name))
skip = false
end
end
end
end
if skip
return (nothing, Union{}, false)
end
end

if isa(caller, LambdaInfo)
code = caller
end

if frame === nothing
# inference not started yet, make a new frame for a new lambda
# add lam to be inferred and record the edge

if caller === nothing && in_typeinf_loop
# if the caller needed the ast, but we are already in the typeinf loop
# then just return early -- we can't fulfill this request
# if the client was inlining, then this means we decided not to try to infer this
# particular signature (due to signature coarsening in abstract_call_gf_by_type)
# and attempting to force it now would be a bad idea (non terminating)
skip = true
if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.module == Main.Base)
# however, some gf have special tfunc and meaning they wouldn't have been inferred yet
# check the same conditions from abstract_call to detect this case
if method.name == :promote_type || method.name == :typejoin
skip = false
elseif method.name == :getindex || method.name == :next || method.name == :indexed_next
argtypes = atypes.parameters
if length(argtypes)>2 && argtypes[3] Int
at2 = widenconst(argtypes[2])
if (at2 <: Tuple ||
(isa(at2, DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) &&
(at2::DataType).name === Main.Base.Pair.name))
skip = false
end
end
end
end
if skip
return (nothing, Union{}, false)
end
if isa(code, LambdaInfo) && code.code !== nothing
# reuse the existing code object
linfo = code
@assert typeseq(linfo.specTypes, atypes)
elseif method.isstaged
if !isleaftype(atypes)
# don't call staged functions on abstract types.
# (see issues #8504, #10230)
# we can't guarantee that their type behavior is monotonic.
return (nothing, Any, false)
end
try
# user code might throw errors – ignore them
linfo = specialize_method(method, atypes, sparams, cached)
catch
return (nothing, Any, false)
end
else
linfo = specialize_method(method, atypes, sparams, cached)
end

if isa(code, LambdaInfo) && code.code !== nothing
# reuse the existing code object
linfo = code
@assert typeseq(linfo.specTypes, atypes)
elseif method.isstaged
if !isleaftype(atypes)
# don't call staged functions on abstract types.
# (see issues #8504, #10230)
# we can't guarantee that their type behavior is monotonic.
return (nothing, Any, false)
end
try
# user code might throw errors – ignore them
linfo = specialize_method(method, atypes, sparams)
catch
return (nothing, Any, false)
if linfo.inInference
# inference on this signature may be in progress,
# find the corresponding frame in the active list
for infstate in active
infstate === nothing && continue
infstate = infstate::InferenceState
if linfo === infstate.linfo
frame = infstate
break
end
else
linfo = specialize_method(method, atypes, sparams)
end
# our stack frame inference context
# TODO: this assertion seems iffy
assert(frame !== nothing)
else
# inference not started yet, make a new frame for a new lambda
linfo.inInference = true
frame = InferenceState(unshare_linfo!(linfo::LambdaInfo), optimize)
if cached
frame.tfunc_bp = ccall(:jl_specializations_insert, Ref{TypeMapEntry}, (Any, Any, Any), method, atypes, linfo)
end
end
frame = frame::InferenceState

if !isa(caller, Void) && !isa(caller, LambdaInfo)
# if we were called from inside inference,
# the caller will be the InferenceState object
if isa(caller, InferenceState)
# if we were called from inside inference, the caller will be the InferenceState object
# for which the edge was required
caller = caller::InferenceState
if haskey(caller.edges, frame)
Expand Down Expand Up @@ -1554,26 +1544,30 @@ function typeinf_ext(linfo::LambdaInfo)
if isdefined(linfo, :def)
# method lambda - infer this specialization via the method cache
(code, _t, _) = typeinf_edge(linfo.def, linfo.specTypes, linfo.sparam_vals, true, true, true, linfo)
if code.inferred
if code.inferred && linfo !== code
# This case occurs when the IR for a function has been deleted.
# `code` will be a newly-created LambdaInfo, and we need to copy its
# contents to the existing one to copy the info to the method cache.
linfo.inferred = true
linfo.inInference = false
if linfo !== code
linfo.code = code.code
linfo.slotnames = code.slotnames
linfo.slottypes = code.slottypes
linfo.slotflags = code.slotflags
linfo.ssavaluetypes = code.ssavaluetypes
linfo.rettype = code.rettype
linfo.pure = code.pure
end
end
linfo.code = code.code
linfo.slotnames = code.slotnames
linfo.slottypes = code.slottypes
linfo.slotflags = code.slotflags
linfo.ssavaluetypes = code.ssavaluetypes
linfo.rettype = code.rettype
linfo.pure = code.pure
linfo.inlineable = code.inlineable
end
return code
else
# toplevel lambda - infer directly
linfo.inInference = true
frame = InferenceState(linfo, true)
typeinf_loop(frame)
@assert frame.inferred # TODO: deal with this better
return linfo
end
nothing
end


Expand Down Expand Up @@ -1947,20 +1941,6 @@ function finish(me::InferenceState)
end
me.linfo.rettype = me.bestguess

if me.destination !== me.linfo
out = me.destination
out.inferred = true
out.inInference = false
out.code = me.linfo.code
out.slotnames = me.linfo.slotnames
out.slottypes = me.linfo.slottypes
out.slotflags = me.linfo.slotflags
out.ssavaluetypes = me.linfo.ssavaluetypes
out.rettype = me.linfo.rettype
out.pure = me.linfo.pure
out.inlineable = me.linfo.inlineable
end

# lazy-delete the item from active for several reasons:
# efficiency, correctness, and recursion-safety
nactive[] -= 1
Expand Down
2 changes: 1 addition & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ void *jl_get_llvmf(jl_tupletype_t *tt, bool getwrapper, bool getdeclarations)

if (linfo->code == jl_nothing) {
// re-infer if we've deleted the code
jl_type_infer(linfo, 0);
linfo = jl_type_infer(linfo, 0);
if (linfo->code == jl_nothing) {
JL_GC_POP();
return NULL;
Expand Down
Loading

2 comments on commit 4accf8a

@jrevels
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref Tim's comment here, and here (I think)

@timholy We'll be double-submitting jobs (if I read your intent in that other comment correctly), but no harm in that. You can submit jobs on specific commits by commenting the trigger phrase on that commit. The UI for PRs is nicer though, since GitHub actually shows icons for the statuses there. The Nanosoldier will reply here with a comment after this job is done.

@nanosoldier runbenchmarks(ALL, vs=":release-0.4")

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels

Please sign in to comment.