diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index e3e3502d661736..f8ea5fece9669c 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -802,10 +802,10 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, # otherwise, we don't # check in the cycle list first - # all items in here are mutual parents of all others + # all items in here are considered mutual parents of all others if !any(p::AbsIntState->matches_sv(p, sv), callers_in_cycle(frame)) let parent = frame_parent(frame) - parent !== nothing || return false + parent === nothing && return false (is_cached(parent) || frame_parent(parent) !== nothing) || return false matches_sv(parent, sv) || return false end @@ -1307,7 +1307,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, if code !== nothing irsv = IRInterpretationState(interp, code, mi, arginfo.argtypes, world) if irsv !== nothing - irsv.parent = sv + assign_parentchild(irsv, sv) rt, (nothrow, noub) = ir_abstract_constant_propagation(interp, irsv) @assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from irinterp" if !(isa(rt, Type) && hasintersect(rt, Bool)) @@ -1385,11 +1385,17 @@ function const_prop_call(interp::AbstractInterpreter, add_remark!(interp, sv, "[constprop] Could not retrieve the source") return nothing # this is probably a bad generated function (unsound), but just ignore it end - frame.parent = sv + assign_parentchild(frame, sv) if !typeinf(interp, frame) add_remark!(interp, sv, "[constprop] Fresh constant inference hit a cycle") + @assert frame.frameid != 0 && frame.cycleid == frame.frameid + callstack = frame.callstack::Vector{AbsIntState} + @assert callstack[end] === frame && length(callstack) == frame.frameid + pop!(callstack) return nothing end + @assert frame.frameid != 0 && frame.cycleid == frame.frameid + @assert frame.parentid == sv.frameid @assert inf_result.result !== nothing # ConditionalSimpleArgtypes is allowed, because the only case in which it modifies # the argtypes is when one of the argtypes is a `Conditional`, which case @@ -3306,7 +3312,6 @@ end # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !is_inferred(frame) - frame.dont_work_on_me = true # mark that this function is currently on the stack W = frame.ip ssavaluetypes = frame.ssavaluetypes bbs = frame.cfg.blocks @@ -3527,7 +3532,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end end # while currbb <= nbbs - frame.dont_work_on_me = false nothing end @@ -3579,16 +3583,23 @@ end # make as much progress on `frame` as possible (by handling cycles) function typeinf_nocycle(interp::AbstractInterpreter, frame::InferenceState) typeinf_local(interp, frame) + @assert isempty(frame.ip) + callstack = frame.callstack::Vector{AbsIntState} + frame.cycleid == length(callstack) && return true - # 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 + while true + # If the current frame is not the top part of a cycle, continue to the top of the cycle before resuming work + frame.cycleid == frame.frameid || return false + # If done, return and finalize this cycle + no_active_ips_in_callers && return true + # Otherwise, do at least one iteration over the entire current cycle no_active_ips_in_callers = true - for caller in frame.callers_in_cycle - caller.dont_work_on_me && return false # cycle is above us on the stack + for i = reverse(frame.cycleid:length(callstack)) + caller = callstack[i]::InferenceState if !isempty(caller.ip) # Note that `typeinf_local(interp, caller)` can potentially modify the other frames - # `frame.callers_in_cycle`, which is why making incremental progress requires the + # `frame.cycleid`, which is why making incremental progress requires the # outer while loop. typeinf_local(interp, caller) no_active_ips_in_callers = false diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 38011656e41ea6..0ec101da97744f 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -254,9 +254,10 @@ mutable struct InferenceState pclimitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on currpc ssavalue limitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on return cycle_backedges::Vector{Tuple{InferenceState, Int}} # call-graph backedges connecting from callee to caller - callers_in_cycle::Vector{InferenceState} - dont_work_on_me::Bool - parent # ::Union{Nothing,AbsIntState} + callstack #::Vector{AbsIntState} + parentid::Int + frameid::Int + cycleid::Int #= results =# result::InferenceResult # remember where to put the result @@ -324,9 +325,7 @@ mutable struct InferenceState pclimitations = IdSet{InferenceState}() limitations = IdSet{InferenceState}() cycle_backedges = Vector{Tuple{InferenceState,Int}}() - callers_in_cycle = Vector{InferenceState}() - dont_work_on_me = false - parent = nothing + callstack = AbsIntState[] valid_worlds = WorldRange(1, get_world_counter()) bestguess = Bottom @@ -354,7 +353,7 @@ mutable struct InferenceState this = new( mi, world, mod, sptypes, slottypes, src, cfg, method_info, currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, - pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, + pclimitations, limitations, cycle_backedges, callstack, 0, 0, 0, result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, interp) @@ -836,30 +835,6 @@ function empty_backedges!(frame::InferenceState, currpc::Int=frame.currpc) return nothing end -function print_callstack(sv::InferenceState) - print("=================== Callstack: ==================\n") - idx = 0 - while sv !== nothing - print("[") - print(idx) - if !isa(sv.interp, NativeInterpreter) - print(", ") - print(typeof(sv.interp)) - end - print("] ") - print(sv.linfo) - is_cached(sv) || print(" [uncached]") - println() - for cycle in sv.callers_in_cycle - print(' ', cycle.linfo) - println() - end - sv = sv.parent - idx += 1 - end - print("================= End callstack ==================\n") -end - function narguments(sv::InferenceState, include_va::Bool=true) nargs = Int(sv.src.nargs) if !include_va @@ -885,7 +860,9 @@ mutable struct IRInterpretationState const lazyreachability::LazyCFGReachability valid_worlds::WorldRange const edges::Vector{Any} - parent # ::Union{Nothing,AbsIntState} + callstack #::Vector{AbsIntState} + frameid::Int + parentid::Int function IRInterpretationState(interp::AbstractInterpreter, method_info::MethodInfo, ir::IRCode, mi::MethodInstance, argtypes::Vector{Any}, @@ -908,9 +885,9 @@ mutable struct IRInterpretationState lazyreachability = LazyCFGReachability(ir) valid_worlds = WorldRange(min_world, max_world == typemax(UInt) ? get_world_counter() : max_world) edges = Any[] - parent = nothing + callstack = AbsIntState[] return new(method_info, ir, mi, world, curridx, argtypes_refined, ir.sptypes, tpdum, - ssa_refined, lazyreachability, valid_worlds, edges, parent) + ssa_refined, lazyreachability, valid_worlds, edges, callstack, 0, 0) end end @@ -930,11 +907,34 @@ function IRInterpretationState(interp::AbstractInterpreter, codeinst.min_world, codeinst.max_world) end + # AbsIntState # =========== const AbsIntState = Union{InferenceState,IRInterpretationState} +function print_callstack(frame::AbsIntState) + print("=================== Callstack: ==================\n") + frames = frame.callstack::Vector{AbsIntState} + for idx = (frame.frameid == 0 ? 0 : 1):length(frames) + sv = (idx == 0 ? frame : frames[idx]) + idx == frame.frameid && print("*") + print("[") + print(idx) + if sv isa InferenceState && !isa(sv.interp, NativeInterpreter) + print(", ") + print(typeof(sv.interp)) + end + print("] ") + print(frame_instance(sv)) + is_cached(sv) || print(" [uncached]") + sv.parentid == idx - 1 || print(" [parent=", sv.parentid, "]") + println() + @assert sv.frameid == idx + end + print("================= End callstack ==================\n") +end + frame_instance(sv::InferenceState) = sv.linfo frame_instance(sv::IRInterpretationState) = sv.mi @@ -945,8 +945,39 @@ function frame_module(sv::AbsIntState) return def.module end -frame_parent(sv::InferenceState) = sv.parent::Union{Nothing,AbsIntState} -frame_parent(sv::IRInterpretationState) = sv.parent::Union{Nothing,AbsIntState} +function frame_parent(sv::InferenceState) + sv.parentid == 0 && return nothing + callstack = sv.callstack::Vector{AbsIntState} + sv = callstack[sv.cycleid]::InferenceState + sv.parentid == 0 && return nothing + return callstack[sv.parentid] +end +frame_parent(sv::IRInterpretationState) = sv.parentid == 0 ? nothing : (sv.callstack::Vector{AbsIntState})[sv.parentid] + +# add the orphan child to the parent and the parent to the child +function assign_parentchild(child::InferenceState, parent::AbsIntState) + @assert child.frameid == 0 + child.callstack = callstack = parent.callstack::Vector{AbsIntState} + child.parentid = parent.frameid + push!(callstack, child) + child.cycleid = child.frameid = length(callstack) + nothing +end +function assign_parentchild(child::IRInterpretationState, parent::AbsIntState) + @assert child.frameid == 0 + child.callstack = callstack = parent.callstack::Vector{AbsIntState} + child.parentid = parent.frameid + push!(callstack, child) + child.frameid = length(callstack) + nothing +end +function assign_parentchild(child::InferenceState, parent::Nothing) + @assert child.frameid == 0 + callstack = child.callstack::Vector{AbsIntState} + push!(callstack, child) + child.cycleid = child.frameid = length(callstack) + nothing +end function is_constproped(sv::InferenceState) (;overridden_by_const) = sv.result @@ -966,9 +997,6 @@ method_for_inference_limit_heuristics(sv::AbsIntState) = method_info(sv).method_ frame_world(sv::InferenceState) = sv.world frame_world(sv::IRInterpretationState) = sv.world -callers_in_cycle(sv::InferenceState) = sv.callers_in_cycle -callers_in_cycle(sv::IRInterpretationState) = () - function is_effect_overridden(sv::AbsIntState, effect::Symbol) if is_effect_overridden(frame_instance(sv), effect) return true @@ -1005,20 +1033,39 @@ Note that cycles may be visited in any order. struct AbsIntStackUnwind sv::AbsIntState end -iterate(unw::AbsIntStackUnwind) = (unw.sv, (unw.sv, 0)) -function iterate(unw::AbsIntStackUnwind, (sv, cyclei)::Tuple{AbsIntState, Int}) - # iterate through the cycle before walking to the parent - callers = callers_in_cycle(sv) - if callers !== () && cyclei < length(callers) - cyclei += 1 - parent = callers[cyclei] - else - cyclei = 0 - parent = frame_parent(sv) +iterate(unw::AbsIntStackUnwind) = (unw.sv, length(unw.sv.callstack::Vector{AbsIntState})) +function iterate(unw::AbsIntStackUnwind, frame::Int) + frame == 0 && return nothing + return ((unw.sv.callstack::Vector{AbsIntState})[frame], frame - 1) +end + +struct AbsIntCycle + frames::Vector{AbsIntState} + cycleid::Int + cycletop::Int +end +iterate(unw::AbsIntCycle) = unw.cycleid == 0 ? nothing : (unw.frames[unw.cycletop], unw.cycletop) +function iterate(unw::AbsIntCycle, frame::Int) + frame == unw.cycleid && return nothing + return (unw.frames[frame - 1], frame - 1) +end + +""" + callers_in_cycle(sv::AbsIntState) + +Iterate through all callers of the given `AbsIntState` in the abstract +interpretation stack (including the given `AbsIntState` itself) that are part +of the same cycle, only if it is part of a cycle with multiple frames. +""" +function callers_in_cycle(sv::InferenceState) + callstack = sv.callstack::Vector{AbsIntState} + cycletop = cycleid = sv.cycleid + while cycletop < length(callstack) && (callstack[cycletop + 1]::InferenceState).cycleid == cycleid + cycletop += 1 end - parent === nothing && return nothing - return (parent, (parent, cyclei)) + AbsIntCycle(callstack, cycletop == cycleid ? 0 : cycleid, cycletop) end +callers_in_cycle(sv::IRInterpretationState) = AbsIntCycle(sv.callstack::Vector{AbsIntState}, 0, 0) # temporarily accumulate our edges to later add as backedges in the callee function add_backedge!(caller::InferenceState, mi::MethodInstance) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 83881354e494e2..95eb7a24a44224 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -24,7 +24,7 @@ function concrete_eval_invoke(interp::AbstractInterpreter, ci::CodeInstance, arg end newirsv = IRInterpretationState(interp, ci, mi, argtypes, world) if newirsv !== nothing - newirsv.parent = parent + assign_parentchild(newirsv, parent) return ir_abstract_constant_propagation(interp, newirsv) end return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) @@ -440,6 +440,12 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR store_backedges(frame_instance(irsv), irsv.edges) end + if irsv.frameid != 0 + callstack = irsv.callstack::Vector{AbsIntState} + @assert callstack[end] === irsv && length(callstack) == irsv.frameid + pop!(callstack) + end + return Pair{Any,Tuple{Bool,Bool}}(maybe_singleton_const(ultimate_rt), (nothrow, noub)) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 85bdd881042dc2..35f3054bfee99f 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -261,36 +261,37 @@ end function _typeinf(interp::AbstractInterpreter, frame::InferenceState) typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle # with no active ip's, frame is done - frames = frame.callers_in_cycle - if isempty(frames) - finish_nocycle(interp, frame) - elseif length(frames) == 1 - @assert frames[1] === frame "invalid callers_in_cycle" + frames = frame.callstack::Vector{AbsIntState} + if length(frames) == frame.cycleid finish_nocycle(interp, frame) else - finish_cycle(interp, frames) + @assert frame.cycleid != 0 + finish_cycle(interp, frames, frame.cycleid) end - empty!(frames) return true end function finish_nocycle(::AbstractInterpreter, frame::InferenceState) - frame.dont_work_on_me = true finishinfer!(frame, frame.interp) opt = frame.result.src if opt isa OptimizationState # implies `may_optimize(caller.interp) === true` optimize(frame.interp, opt, frame.result) end finish!(frame.interp, frame) + if frame.cycleid != 0 + frames = frame.callstack::Vector{AbsIntState} + @assert frames[end] === frame + pop!(frames) + end return nothing end -function finish_cycle(::AbstractInterpreter, frames::Vector{InferenceState}) +function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cycleid::Int) cycle_valid_worlds = WorldRange() cycle_valid_effects = EFFECTS_TOTAL - for caller in frames - @assert !(caller.dont_work_on_me) - caller.dont_work_on_me = true + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState + @assert caller.cycleid == cycleid # converge the world age range and effects for this cycle here: # all frames in the cycle should have the same bits of `valid_worlds` and `effects` # that are simply the intersection of each partial computation, without having @@ -298,19 +299,23 @@ function finish_cycle(::AbstractInterpreter, frames::Vector{InferenceState}) cycle_valid_worlds = intersect(cycle_valid_worlds, caller.valid_worlds) cycle_valid_effects = merge_effects(cycle_valid_effects, caller.ipo_effects) end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState adjust_cycle_frame!(caller, cycle_valid_worlds, cycle_valid_effects) finishinfer!(caller, caller.interp) end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState opt = caller.result.src if opt isa OptimizationState # implies `may_optimize(caller.interp) === true` optimize(caller.interp, opt, caller.result) end end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState finish!(caller.interp, caller) end + resize!(frames, cycleid - 1) return nothing end @@ -396,9 +401,9 @@ end function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) if typ isa LimitedAccuracy - if sv.parent === nothing + if sv.parentid === 0 # we might have introduced a limit marker, but we should know it must be sv and other callers_in_cycle - #@assert !isempty(sv.callers_in_cycle) + #@assert !isempty(callers_in_cycle(sv)) # FIXME: this assert fails, appearing to indicate there is a bug in filtering this list earlier. # In particular (during doctests for example), during inference of # show(Base.IOContext{Base.GenericIOBuffer{Memory{UInt8}}}, Base.Multimedia.MIME{:var"text/plain"}, LinearAlgebra.BunchKaufman{Float64, Array{Float64, 2}, Array{Int64, 1}}) @@ -407,7 +412,7 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) end causes = copy(typ.causes) delete!(causes, sv) - for caller in sv.callers_in_cycle + for caller in callers_in_cycle(sv) delete!(causes, caller) end if isempty(causes) @@ -518,6 +523,7 @@ end # update the MethodInstance function finishinfer!(me::InferenceState, interp::AbstractInterpreter) # prepare to run optimization passes on fulltree + @assert isempty(me.ip) s_edges = get_stmt_edges!(me, 1) for i = 2:length(me.stmt_edges) isassigned(me.stmt_edges, i) || continue @@ -538,7 +544,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) gt = me.ssavaluetypes for j = 1:length(gt) gt[j] = gtj = cycle_fix_limited(gt[j], me) - if gtj isa LimitedAccuracy && me.parent !== nothing + if gtj isa LimitedAccuracy && me.parentid != 0 limited_src = true break end @@ -571,7 +577,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) mayopt = may_optimize(interp) doopt = mayopt && # disable optimization if we don't use this later - (me.cache_mode != CACHE_MODE_NULL || me.parent !== nothing) && + (me.cache_mode != CACHE_MODE_NULL || me.parentid != 0) && # disable optimization if we've already obtained very accurate result !result_is_constabi(interp, result, mayopt) if doopt @@ -743,41 +749,29 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState) return nothing 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!(interp::AbstractInterpreter, parent::InferenceState, ancestor::InferenceState, child::InferenceState) +function merge_call_chain!(interp::AbstractInterpreter, parent::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 + frames = parent.callstack::Vector{AbsIntState} + @assert child.callstack === frames + ancestorid = child.cycleid while true add_cycle_backedge!(parent, child) - union_caller_cycle!(ancestor, child) + parent.cycleid === ancestorid && break child = parent - child === ancestor && break parent = frame_parent(child) while !isa(parent, InferenceState) # XXX we may miss some edges here? parent = frame_parent(parent::IRInterpretationState) end - parent = parent::InferenceState + end + # ensure that walking the callstack has the same cycleid (DAG) + for frame = reverse(ancestorid:length(frames)) + frame = frames[frame] + frame isa InferenceState || continue + frame.cycleid == ancestorid && break + @assert frame.cycleid > ancestorid + frame.cycleid = ancestorid end end @@ -793,8 +787,8 @@ end # Walk through `mi`'s upstream call chain, starting at `parent`. If a parent # frame matching `mi` is encountered, then there is a cycle in the call graph # (i.e. `mi` 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 "resolve" it by merging the call chain, which entails updating each intermediary +# frame's `cycleid` field and adding the appropriate backedges. Finally, # we return `mi`'s pre-existing frame. If no cycles are found, `nothing` is # returned instead. function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, parent::AbsIntState) @@ -802,10 +796,11 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa # This works just because currently the `:terminate` condition guarantees that # irinterp doesn't fail into unresolved cycles, but it's not a good solution. # We should revisit this once we have a better story for handling cycles in irinterp. - isa(parent, InferenceState) || return false - frame = parent + frames = parent.callstack::Vector{AbsIntState} uncached = false - while isa(frame, InferenceState) + for frame = reverse(1:length(frames)) + frame = frames[frame] + isa(frame, InferenceState) || break uncached |= !is_cached(frame) # ensure we never add an uncached frame to a cycle if is_same_frame(interp, mi, frame) if uncached @@ -815,20 +810,9 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa poison_callstack!(parent, frame) return true end - merge_call_chain!(interp, parent, frame, frame) + merge_call_chain!(interp, parent, frame) return frame end - for caller in callers_in_cycle(frame) - if is_same_frame(interp, mi, caller) - if uncached - poison_callstack!(parent, frame) - return true - end - merge_call_chain!(interp, parent, frame, caller) - return caller - end - end - frame = frame_parent(frame) end return false end @@ -917,9 +901,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize end return EdgeCallResult(Any, Any, nothing, Effects()) end - if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution - frame.parent = caller - end + assign_parentchild(frame, caller) typeinf(interp, frame) update_valid_age!(caller, frame.valid_worlds) isinferred = is_inferred(frame) @@ -1195,6 +1177,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod engine_reject(interp, ci) return nothing end + assign_parentchild(frame, nothing) typeinf(interp, frame) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) @@ -1257,6 +1240,7 @@ function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance) engine_reject(interp, ci) return nothing end + assign_parentchild(frame, nothing) typeinf(interp, frame) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) is_inferred(result) || return nothing diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 609a7b4d81bc08..4051487ad1a3a8 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -604,7 +604,7 @@ is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode function is_call_graph_uncached(sv::CC.InferenceState) CC.is_cached(sv) && return false - parent = sv.parent + parent = CC.frame_parent(sv) parent === nothing && return true return is_call_graph_uncached(parent::CC.InferenceState) end @@ -627,7 +627,7 @@ function is_repl_frame_getproperty(sv::CC.InferenceState) def isa Method || return false def.name === :getproperty || return false CC.is_cached(sv) && return false - return is_repl_frame(sv.parent) + return is_repl_frame(CC.frame_parent(sv)) end # aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`