Skip to content

Commit 0366a2a

Browse files
authored
[Compiler] fix some cycle_fix_limited usage (#57545)
Recompute some O(n) things a bit less on every statement. Fix an assert that ensured LimitedAccuracy does not accidentally get preserved when it should have been deleted: the (local) cache should not contain things that are marked as dead (RIP), as that was leading to much code not getting cached when it logically should have. Simplify the computation of frame_parent when the cycle_parent isn't needed.
1 parent 0b43e97 commit 0366a2a

File tree

6 files changed

+45
-46
lines changed

6 files changed

+45
-46
lines changed

Compiler/src/abstractinterpretation.jl

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -286,19 +286,12 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun
286286
state.rettype = Any
287287
end
288288
# if from_interprocedural added any pclimitations to the set inherited from the arguments,
289-
# some of those may be part of our cycles, so those can be deleted now
290-
# TODO: and those might need to be deleted later too if the cycle grows to include them?
291289
if isa(sv, InferenceState)
292290
# TODO (#48913) implement a proper recursion handling for irinterp:
293-
# This works just because currently the `:terminate` condition guarantees that
294-
# irinterp doesn't fail into unresolved cycles, but it's not a good solution.
291+
# This works most of the time just because currently the `:terminate` condition often guarantees that
292+
# irinterp doesn't fail into unresolved cycles, but it is not a good (or working) solution.
295293
# We should revisit this once we have a better story for handling cycles in irinterp.
296-
if !isempty(sv.pclimitations) # remove self, if present
297-
delete!(sv.pclimitations, sv)
298-
for caller in callers_in_cycle(sv)
299-
delete!(sv.pclimitations, caller)
300-
end
301-
end
294+
delete!(sv.pclimitations, sv) # remove self, if present
302295
end
303296
else
304297
# there is unanalyzed candidate, widen type and effects to the top
@@ -775,7 +768,7 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState,
775768
# check in the cycle list first
776769
# all items in here are considered mutual parents of all others
777770
if !any(p::AbsIntState->matches_sv(p, sv), callers_in_cycle(frame))
778-
let parent = frame_parent(frame)
771+
let parent = cycle_parent(frame)
779772
parent === nothing && return false
780773
(is_cached(parent) || frame_parent(parent) !== nothing) || return false
781774
matches_sv(parent, sv) || return false
@@ -1379,6 +1372,7 @@ function const_prop_call(interp::AbstractInterpreter,
13791372
inf_result.result = concrete_eval_result.rt
13801373
inf_result.ipo_effects = concrete_eval_result.effects
13811374
end
1375+
typ = inf_result.result
13821376
return const_prop_result(inf_result)
13831377
end
13841378

Compiler/src/inferenceresult.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ function cache_lookup(𝕃::AbstractLattice, mi::MethodInstance, given_argtypes:
183183
method = mi.def::Method
184184
nargtypes = length(given_argtypes)
185185
for cached_result in cache
186-
cached_result.linfo === mi || @goto next_cache
186+
cached_result.tombstone && continue # ignore deleted entries (due to LimitedAccuracy)
187+
cached_result.linfo === mi || continue
187188
cache_argtypes = cached_result.argtypes
188189
@assert length(cache_argtypes) == nargtypes "invalid `cache_argtypes` for `mi`"
189190
cache_overridden_by_const = cached_result.overridden_by_const::BitVector

Compiler/src/inferencestate.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ mutable struct InferenceState
292292

293293
# IPO tracking of in-process work, shared with all frames given AbstractInterpreter
294294
callstack #::Vector{AbsIntState}
295-
parentid::Int # index into callstack of the parent frame that originally added this frame (call frame_parent to extract the current parent of the SCC)
295+
parentid::Int # index into callstack of the parent frame that originally added this frame (call cycle_parent to extract the current parent of the SCC)
296296
frameid::Int # index into callstack at which this object is found (or zero, if this is not a cached frame and has no parent)
297297
cycleid::Int # index into the callstack of the topmost frame in the cycle (all frames in the same cycle share the same cycleid)
298298

@@ -908,14 +908,17 @@ function frame_module(sv::AbsIntState)
908908
return def.module
909909
end
910910

911-
function frame_parent(sv::InferenceState)
911+
frame_parent(sv::AbsIntState) = sv.parentid == 0 ? nothing : (sv.callstack::Vector{AbsIntState})[sv.parentid]
912+
913+
function cycle_parent(sv::InferenceState)
912914
sv.parentid == 0 && return nothing
913915
callstack = sv.callstack::Vector{AbsIntState}
914916
sv = callstack[sv.cycleid]::InferenceState
915917
sv.parentid == 0 && return nothing
916918
return callstack[sv.parentid]
917919
end
918-
frame_parent(sv::IRInterpretationState) = sv.parentid == 0 ? nothing : (sv.callstack::Vector{AbsIntState})[sv.parentid]
920+
cycle_parent(sv::IRInterpretationState) = frame_parent(sv)
921+
919922

920923
# add the orphan child to the parent and the parent to the child
921924
function assign_parentchild!(child::InferenceState, parent::AbsIntState)

Compiler/src/ssair/irinterp.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
function collect_limitations!(@nospecialize(typ), ::IRInterpretationState)
4-
@assert !isa(typ, LimitedAccuracy) "irinterp is unable to handle heavy recursion"
4+
@assert !isa(typ, LimitedAccuracy) "irinterp is unable to handle heavy recursion correctly"
55
return typ
66
end
77

@@ -212,6 +212,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction,
212212
else
213213
rt = argextype(stmt, irsv.ir)
214214
end
215+
@assert !(rt isa LimitedAccuracy)
215216
if rt !== nothing
216217
if has_flag(inst, IR_FLAG_UNUSED)
217218
# Don't bother checking the type if we know it's unused

Compiler/src/typeinfer.jl

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ function finish!(interp::AbstractInterpreter, mi::MethodInstance, ci::CodeInstan
195195
end
196196

197197
function finish_nocycle(::AbstractInterpreter, frame::InferenceState)
198-
finishinfer!(frame, frame.interp)
198+
finishinfer!(frame, frame.interp, frame.cycleid)
199199
opt = frame.result.src
200200
if opt isa OptimizationState # implies `may_optimize(caller.interp) === true`
201201
optimize(frame.interp, opt, frame.result)
@@ -232,7 +232,7 @@ function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cyclei
232232
for frameid = cycleid:length(frames)
233233
caller = frames[frameid]::InferenceState
234234
adjust_cycle_frame!(caller, cycle_valid_worlds, cycle_valid_effects)
235-
finishinfer!(caller, caller.interp)
235+
finishinfer!(caller, caller.interp, cycleid)
236236
end
237237
for frameid = cycleid:length(frames)
238238
caller = frames[frameid]::InferenceState
@@ -312,26 +312,21 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult, ci:
312312
return true
313313
end
314314

315-
function cycle_fix_limited(@nospecialize(typ), sv::InferenceState)
315+
function cycle_fix_limited(@nospecialize(typ), sv::InferenceState, cycleid::Int)
316316
if typ isa LimitedAccuracy
317-
if sv.parentid === 0
318-
# we might have introduced a limit marker, but we should know it must be sv and other callers_in_cycle
319-
#@assert !isempty(callers_in_cycle(sv))
320-
# FIXME: this assert fails, appearing to indicate there is a bug in filtering this list earlier.
321-
# In particular (during doctests for example), during inference of
322-
# show(Base.IOContext{Base.GenericIOBuffer{Memory{UInt8}}}, Base.Multimedia.MIME{:var"text/plain"}, LinearAlgebra.BunchKaufman{Float64, Array{Float64, 2}, Array{Int64, 1}})
323-
# we observed one of the ssavaluetypes here to be Core.Compiler.LimitedAccuracy(typ=Any, causes=Core.Compiler.IdSet(getproperty(LinearAlgebra.BunchKaufman{Float64, Array{Float64, 2}, Array{Int64, 1}}, Symbol)))
324-
return typ.typ
325-
end
326-
causes = copy(typ.causes)
327-
delete!(causes, sv)
328-
for caller in callers_in_cycle(sv)
329-
delete!(causes, caller)
330-
end
331-
if isempty(causes)
332-
return typ.typ
317+
frames = sv.callstack::Vector{AbsIntState}
318+
causes = typ.causes
319+
for frameid = cycleid:length(frames)
320+
caller = frames[frameid]::InferenceState
321+
caller in causes || continue
322+
causes === typ.causes && (causes = copy(causes))
323+
pop!(causes, caller)
324+
if isempty(causes)
325+
return typ.typ
326+
end
333327
end
334-
if length(causes) != length(typ.causes)
328+
@assert sv.parentid != 0
329+
if causes !== typ.causes
335330
return LimitedAccuracy(typ.typ, causes)
336331
end
337332
end
@@ -439,20 +434,23 @@ const empty_edges = Core.svec()
439434

440435
# inference completed on `me`
441436
# update the MethodInstance
442-
function finishinfer!(me::InferenceState, interp::AbstractInterpreter)
437+
function finishinfer!(me::InferenceState, interp::AbstractInterpreter, cycleid::Int)
443438
# prepare to run optimization passes on fulltree
444439
@assert isempty(me.ip)
445440
# inspect whether our inference had a limited result accuracy,
446441
# else it may be suitable to cache
447-
bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me)
448-
exc_bestguess = me.exc_bestguess = cycle_fix_limited(me.exc_bestguess, me)
442+
bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me, cycleid)
443+
exc_bestguess = me.exc_bestguess = cycle_fix_limited(me.exc_bestguess, me, cycleid)
449444
limited_ret = bestguess isa LimitedAccuracy || exc_bestguess isa LimitedAccuracy
450445
limited_src = false
451-
if !limited_ret
446+
if limited_ret
447+
@assert me.parentid != 0
448+
else
452449
gt = me.ssavaluetypes
453450
for j = 1:length(gt)
454-
gt[j] = gtj = cycle_fix_limited(gt[j], me)
455-
if gtj isa LimitedAccuracy && me.parentid != 0
451+
gt[j] = gtj = cycle_fix_limited(gt[j], me, cycleid)
452+
if gtj isa LimitedAccuracy
453+
@assert me.parentid != 0
456454
limited_src = true
457455
break
458456
end
@@ -474,6 +472,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter)
474472
# a parent may be cached still, but not this intermediate work:
475473
# we can throw everything else away now
476474
result.src = nothing
475+
result.tombstone = true
477476
me.cache_mode = CACHE_MODE_NULL
478477
set_inlineable!(me.src, false)
479478
elseif limited_src
@@ -714,7 +713,7 @@ function merge_call_chain!(::AbstractInterpreter, parent::InferenceState, child:
714713
add_cycle_backedge!(parent, child)
715714
parent.cycleid === ancestorid && break
716715
child = parent
717-
parent = frame_parent(child)::InferenceState
716+
parent = cycle_parent(child)::InferenceState
718717
end
719718
# ensure that walking the callstack has the same cycleid (DAG)
720719
for frameid = reverse(ancestorid:length(frames))
@@ -750,7 +749,7 @@ end
750749
# returned instead.
751750
function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, parent::AbsIntState)
752751
# TODO (#48913) implement a proper recursion handling for irinterp:
753-
# This works currently just because the irinterp code doesn't get used much with
752+
# This works most of the time currently just because the irinterp code doesn't get used much with
754753
# `@assume_effects`, so it never sees a cycle normally, but that may not be a sustainable solution.
755754
parent isa InferenceState || return false
756755
frames = parent.callstack::Vector{AbsIntState}
@@ -762,7 +761,7 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa
762761
if is_same_frame(interp, mi, frame)
763762
if uncached
764763
# our attempt to speculate into a constant call lead to an undesired self-cycle
765-
# that cannot be converged: poison our call-stack (up to the discovered duplicate frame)
764+
# that cannot be converged: if necessary, poison our call-stack (up to the discovered duplicate frame)
766765
# with the limited flag and abort (set return type to Any) now
767766
poison_callstack!(parent, frame)
768767
return true

Compiler/src/types.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ mutable struct InferenceResult
110110
effects::Effects # if optimization is finished
111111
analysis_results::AnalysisResults # AnalysisResults with e.g. result::ArgEscapeCache if optimized, otherwise NULL_ANALYSIS_RESULTS
112112
is_src_volatile::Bool # `src` has been cached globally as the compressed format already, allowing `src` to be used destructively
113+
tombstone::Bool
113114

114115
#=== uninitialized fields ===#
115116
ci::CodeInstance # CodeInstance if this result may be added to the cache
@@ -120,7 +121,7 @@ mutable struct InferenceResult
120121
ipo_effects = effects = Effects()
121122
analysis_results = NULL_ANALYSIS_RESULTS
122123
return new(mi, argtypes, overridden_by_const, result, exc_result, src,
123-
valid_worlds, ipo_effects, effects, analysis_results, #=is_src_volatile=#false)
124+
valid_worlds, ipo_effects, effects, analysis_results, #=is_src_volatile=#false, false)
124125
end
125126
end
126127
function InferenceResult(mi::MethodInstance, 𝕃::AbstractLattice=fallback_lattice)

0 commit comments

Comments
 (0)