/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Compiler/src/stmtinfo.jl
727 строк
26 KB
Keno Fischer
compiler: Separate local inference proofs from code instances (#62359)
05 авг 2026, 07:29
Не верифицирован
05 авг 2026, 07:29
311f373
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license @nospecialize """ call::CallMeta A simple struct that captures both the return type (`call.rt`) and any additional information (`call.info`) for a given generic call. """ struct CallMeta rt::Any exct::Any effects::Effects info::CallInfo refinements # ::Union{Nothing,SlotRefinement,Vector{Any}} function CallMeta(rt::Any, exct::Any, effects::Effects, info::CallInfo, refinements=nothing) @nospecialize rt exct info return new(rt, exct, effects, info, refinements) end end struct NoCallInfo <: CallInfo end add_edges_impl(::Vector{Any}, ::NoCallInfo) = nothing # InferredCallResult is defined in types.jl with LocalInferenceResult struct ConcreteResult <: InferredCallResult edge::Union{Nothing,CodeInstance} effects::Effects proof::Union{Nothing,InferenceProof} result function ConcreteResult(edge::Union{Nothing,CodeInstance}, effects::Effects; proof::Union{Nothing,InferenceProof}=nothing) @assert edge !== nothing || proof !== nothing return new(edge, effects, proof) end function ConcreteResult(edge::Union{Nothing,CodeInstance}, effects::Effects, @nospecialize(val); proof::Union{Nothing,InferenceProof}=nothing) @assert edge !== nothing || proof !== nothing return new(edge, effects, proof, val) end end struct SemiConcreteResult <: InferredCallResult edge::CodeInstance ir::IRCode effects::Effects spec_info::SpecInfo proof::Union{Nothing,InferenceProof} SemiConcreteResult(edge::CodeInstance, ir::IRCode, effects::Effects, spec_info::SpecInfo; proof::Union{Nothing,InferenceProof}=nothing) = new(edge, ir, effects, spec_info, proof) end inference_proof(result::ConcreteResult) = result.proof === nothing ? result.edge::CodeInstance : result.proof inference_proof(result::SemiConcreteResult) = something(result.proof, result.edge) function record_invoke_edge!(invokes::IdDict{Any,Vector{Any}}, @nospecialize(signature), @nospecialize(target)) signatures = get!(Vector{Any}, invokes, target) for previous in signatures previous == signature && return false end push!(signatures, signature) return true end function _materialize_inference_edges!(edges::Vector{Any}, source, seen_proofs::IdSet{LocalInferenceProof}, standalone::IdSet{Any}, invokes::IdDict{Any,Vector{Any}}) i = 1 while i <= length(source) edge = source[i] if edge isa LocalInferenceProof i += 1 edge in seen_proofs && continue push!(seen_proofs, edge) _materialize_inference_edges!( edges, edge.edges, seen_proofs, standalone, invokes) elseif edge isa Int # Encoded lookup groups are positional: `nmatches, atype, matches...`. # Preserve each complete group, while recording its targets so an # identical standalone proof edge later in the stream can be omitted. n = abs(edge) last = i + 1 + n @assert last <= length(source) for j = i:last push!(edges, source[j]) end for j = i + 2:last target = source[j] if target isa Union{Method,MethodInstance,CodeInstance,Core.Binding} push!(standalone, target) end end i = last + 1 elseif edge isa Union{Method,MethodInstance,CodeInstance,Core.Binding} i += 1 edge in standalone && continue push!(standalone, edge) push!(edges, edge) else # Everything else is an invoke signature paired with its target. Keep # distinct signatures, and deduplicate only the identical pair. @assert i < length(source) target = source[i + 1] i += 2 record_invoke_edge!(invokes, edge, target) || continue push!(edges, edge, target) end end return nothing end function materialize_inference_edges(source) has_local_proof = false for edge in source if edge isa LocalInferenceProof has_local_proof = true break end end if !has_local_proof return source isa SimpleVector ? source : Core.svec(source...) end edges = Any[] sizehint!(edges, length(source)) _materialize_inference_edges!(edges, source, IdSet{LocalInferenceProof}(), IdSet{Any}(), IdDict{Any,Vector{Any}}()) return Core.svec(edges...) end function add_inference_proof!(edges::Vector{Any}, proof::CodeInstance, @nospecialize(paired_edge=nothing)) # This CI certifies the inferred facts of its defining method; it does not # necessarily represent ordinary dispatch on its MethodInstance signature # (for example, it may have been reached through `invoke`). Encode the proof # as an identity edge to that method, just as we do for an inlined CI. proof === paired_edge || add_inlining_edge!(edges, proof) return nothing end function add_inference_proof!(edges::Vector{Any}, proof::LocalInferenceProof, @nospecialize(paired_edge=nothing)) any(edge -> edge === proof, edges) || push!(edges, proof) return nothing end function add_result_proof!(edges::Vector{Any}, result::Union{Nothing,InferredCallResult}, @nospecialize(paired_edge=nothing)) result === nothing && return nothing add_inference_proof!(edges, inference_proof(result), paired_edge) return nothing end """ info::MethodMatchInfo <: CallInfo Captures the essential arguments and result of a `:jl_matching_methods` lookup for the given call (`info.results`). This info may then be used by the optimizer, without having to re-consult the method table. This info is illegal on any statement that is not a call to a generic function. """ struct MethodMatchInfo <: CallInfo results::MethodLookupResult mt::MethodTable atype fullmatch::Bool edges::Vector{Union{Nothing,CodeInstance}} needs_mi_edges::BitVector call_results::Vector{Union{Nothing,InferredCallResult}} function MethodMatchInfo( results::MethodLookupResult, mt::MethodTable, @nospecialize(atype), fullmatch::Bool) edges = fill!(Vector{Union{Nothing,CodeInstance}}(undef, length(results)), nothing) needs_mi_edges = falses(length(results)) call_results = fill!(Vector{Union{Nothing,InferredCallResult}}(undef, length(results)), nothing) return new(results, mt, atype, fullmatch, edges, needs_mi_edges, call_results) end end add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo) = _add_edges_impl(edges, info) function method_match_edge(info::MethodMatchInfo, i::Int, mi_edge::Bool) edge = info.edges[i] edge !== nothing && return edge match = info.results[i] # A proof-carrying result may carry facts about this specialization without an # executable CI of its own. The same is true when a scheduled call consumed # provisional SCC facts before a CI or local result was available. A completed # tombstone similarly carries its proof separately from the dispatch target. Keep # the MethodInstance as an invalidation target in these cases; a bare Method in an # encoded lookup is intentionally ignored by the backedge iterator and therefore # cannot certify those facts. return (mi_edge || info.needs_mi_edges[i] || info.call_results[i] !== nothing) ? specialize_method(match) : match.method end function has_encoded_lookup(edges::Vector{Any}, info::MethodMatchInfo, encoded_nmatches::Int, mi_edge::Bool) i = 1 while i <= length(edges) entry = edges[i] if entry isa Int n = abs(entry) next_i = i + 2 + n if next_i - 1 <= length(edges) && entry === encoded_nmatches && edges[i + 1] == info.atype matches = true for j = 1:n if edges[i + 1 + j] !== method_match_edge(info, j, mi_edge) matches = false break end end matches && return true end i = next_i else i += 1 end end return false end function add_method_match_proofs!(edges::Vector{Any}, info::MethodMatchInfo, mi_edge::Bool) for i = 1:length(info.call_results) add_result_proof!(edges, info.call_results[i], method_match_edge(info, i, mi_edge)) end return nothing end function _add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo, mi_edge::Bool=false) if !fully_covering(info) exists = false for i in 2:length(edges) if edges[i] === Core.methodtable && edges[i-1] == info.atype exists = true break end end if !exists push!(edges, info.atype) push!(edges, Core.methodtable) end end nmatches = length(info.results) if nmatches == length(info.edges) == 1 && fully_covering(info) # try the optimized format for the representation, if possible and applicable # if this doesn't succeed, the backedge will be less precise, # but the forward edge will maintain the precision edge = info.edges[1] m = info.results[1] if edge === nothing mi = specialize_method(m) # don't allow `Method`-edge for this optimized format edge = mi else mi = edge.def::MethodInstance end if mi.specTypes === m.spec_types add_one_edge!(edges, edge) add_result_proof!(edges, info.call_results[1], edge) return nothing end end # add check for whether this lookup already existed in the edges list # encode nmatches as negative if fully_covers is false encoded_nmatches = fully_covering(info) ? nmatches : -nmatches if !has_encoded_lookup(edges, info, encoded_nmatches, mi_edge) push!(edges, encoded_nmatches, info.atype) for i = 1:nmatches edge = method_match_edge(info, i, mi_edge) if edge isa CodeInstance @assert edge.def.def === info.results[i].method end push!(edges, edge) end end add_method_match_proofs!(edges, info, mi_edge) nothing end function add_one_edge!(edges::Vector{Any}, edge::MethodInstance) i = 1 while i <= length(edges) edgeᵢ = edges[i] edgeᵢ isa Int && (i += 2 + abs(edgeᵢ); continue) edgeᵢ isa CodeInstance && (edgeᵢ = get_ci_mi(edgeᵢ)) edgeᵢ isa MethodInstance || (i += 1; continue) if edgeᵢ === edge && !(i > 1 && edges[i-1] isa Type) return # found existing covered edge end i += 1 end push!(edges, edge) nothing end function add_one_edge!(edges::Vector{Any}, edge::CodeInstance) i = 1 while i <= length(edges) edgeᵢ_orig = edgeᵢ = edges[i] edgeᵢ isa Int && (i += 2 + abs(edgeᵢ); continue) edgeᵢ isa CodeInstance && (edgeᵢ = get_ci_mi(edgeᵢ)) edgeᵢ isa MethodInstance || (i += 1; continue) if edgeᵢ === edge.def && !(i > 1 && edges[i-1] isa Type) if edgeᵢ_orig isa MethodInstance # found edge we can upgrade edges[i] = edge return elseif edgeᵢ_orig === edge # Only the identical CodeInstance certifies the same inference proof. return end # Different CodeInstances for the same MethodInstance may certify # different inference facts. Keep both, irrespective of whether their # current forward-edge streams happen to compare equal. end i += 1 end push!(edges, edge) nothing end nsplit_impl(::MethodMatchInfo) = 1 getsplit_impl(info::MethodMatchInfo, idx::Int) = (@assert idx == 1; info.results) getresult_impl(info::MethodMatchInfo, idx::Int) = info.call_results[idx] getedge_impl(info::MethodMatchInfo, idx::Int) = info.edges[idx] """ info::UnionSplitInfo <: CallInfo If inference decides to partition the method search space by splitting unions, it will issue a method lookup query for each such partition. This info indicates that such partitioning happened and wraps the corresponding `MethodMatchInfo` for each partition (`info.matches::Vector{MethodMatchInfo}`). This info is illegal on any statement that is not a call to a generic function. """ struct UnionSplitInfo <: CallInfo split::Vector{MethodMatchInfo} end add_edges_impl(edges::Vector{Any}, info::UnionSplitInfo) = _add_edges_impl(edges, info) _add_edges_impl(edges::Vector{Any}, info::UnionSplitInfo, mi_edge::Bool=false) = for split in info.split; _add_edges_impl(edges, split, mi_edge); end nsplit_impl(info::UnionSplitInfo) = length(info.split) getsplit_impl(info::UnionSplitInfo, idx::Int) = getsplit(info.split[idx], 1) function getresult_impl(info::UnionSplitInfo, idx::Int) for split in info.split n = length(split.call_results) if idx ≤ n return split.call_results[idx] else idx -= n end end end function getedge_impl(info::UnionSplitInfo, idx::Int) for split in info.split n = length(split.edges) if idx ≤ n return split.edges[idx] else idx -= n end end return nothing end """ info::MethodResultPure <: CallInfo This struct represents a method result that was proven to be a pure (effect-free) constant. """ struct MethodResultPure <: CallInfo info::CallInfo end let instance = MethodResultPure(NoCallInfo()) global MethodResultPure MethodResultPure() = instance end add_edges_impl(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) """ ainfo::AbstractIterationInfo Captures all the information for abstract iteration analysis of a single value. Each (abstract) call to `iterate`, corresponds to one entry in `ainfo.each::Vector{CallMeta}`. """ struct AbstractIterationInfo each::Vector{CallMeta} complete::Bool end const MaybeAbstractIterationInfo = Union{Nothing, AbstractIterationInfo} """ info::ApplyCallInfo <: CallInfo This info applies to any call of `_apply_iterate(...)` and captures both the info of the actual call being applied and the info for any implicit call to the `iterate` function. Note that it is possible for the call itself to be yet another `_apply_iterate`, in which case the `info.call` field will be another `ApplyCallInfo`. This info is illegal on any statement that is not an `_apply_iterate` call. """ struct ApplyCallInfo <: CallInfo # The info for the call itself call::CallInfo # AbstractIterationInfo for each argument, if applicable arginfo::Vector{MaybeAbstractIterationInfo} end function add_edges_impl(edges::Vector{Any}, info::ApplyCallInfo) add_edges!(edges, info.call) for arg in info.arginfo arg === nothing && continue for edge in arg.each add_edges!(edges, edge.info) end end end """ info::UnionSplitApplyCallInfo <: CallInfo Like `UnionSplitInfo`, but for `ApplyCallInfo` rather than `MethodMatchInfo`. This info is illegal on any statement that is not an `_apply_iterate` call. """ struct UnionSplitApplyCallInfo <: CallInfo infos::Vector{ApplyCallInfo} end add_edges_impl(edges::Vector{Any}, info::UnionSplitApplyCallInfo) = for split in info.infos; add_edges!(edges, split); end """ info::InvokeCICallInfo Represents a resolved call to `Core.invoke` targeting a `Core.CodeInstance` """ struct InvokeCICallInfo <: CallInfo edge::CodeInstance end add_edges_impl(edges::Vector{Any}, info::InvokeCICallInfo) = add_inlining_edge!(edges, info.edge) nsplit_impl(::InvokeCICallInfo) = 0 """ info::InvokeCallInfo Represents a resolved call to `Core.invoke`, carrying the `info.match::MethodMatch` of the method that has been processed. Optionally keeps a proof-carrying `info.result` with constant information. """ struct InvokeCallInfo <: CallInfo edge::Union{Nothing,CodeInstance} match::MethodMatch result::Union{Nothing,InferredCallResult} atype # ::Type needs_mi_edge::Bool # targetless body-derived facts require an invalidation target end InvokeCallInfo(edge::Union{Nothing,CodeInstance}, match::MethodMatch, result::Union{Nothing,InferredCallResult}, @nospecialize(atype)) = InvokeCallInfo(edge, match, result, atype, false) add_edges_impl(edges::Vector{Any}, info::InvokeCallInfo) = _add_edges_impl(edges, info) function _add_edges_impl(edges::Vector{Any}, info::InvokeCallInfo, mi_edge::Bool=false) edge = info.edge if edge === nothing edge = (mi_edge || info.needs_mi_edge || info.result !== nothing) ? specialize_method(info.match) : info.match.method end add_invoke_edge!(edges, info.atype, edge) add_result_proof!(edges, info.result, edge) nothing end function add_invoke_edge!(edges::Vector{Any}, @nospecialize(atype), edge::Union{MethodInstance,Method}) i = 1 while i <= length(edges) edgeᵢ = edges[i] if edgeᵢ isa Int i += 2 + abs(edgeᵢ) continue end edgeᵢ isa CodeInstance && (edgeᵢ = edgeᵢ.def) if !(edgeᵢ isa MethodInstance || edgeᵢ isa Method) i += 1 continue end if edgeᵢ === edge i == 1 && (i += 1; continue) edge_minus_1 = edges[i - 1] if edge_minus_1 isa Type && edge_minus_1 == atype return # found existing covered edge end end i += 1 end push!(edges, atype) push!(edges, edge) nothing end function add_invoke_edge!(edges::Vector{Any}, @nospecialize(atype), edge::CodeInstance) i = 1 while i <= length(edges) edgeᵢ_orig = edgeᵢ = edges[i] if edgeᵢ isa Int i += 2 + abs(edgeᵢ) continue end edgeᵢ isa CodeInstance && (edgeᵢ = edgeᵢ.def) if ((edgeᵢ isa MethodInstance && edgeᵢ === edge.def) || (edgeᵢ isa Method && edgeᵢ === edge.def.def)) i == 1 && (i += 1; continue) edge_minus_1 = edges[i - 1] if edge_minus_1 isa Type && edge_minus_1 == atype if edgeᵢ_orig isa MethodInstance || edgeᵢ_orig isa Method # found edge we can upgrade edges[i] = edge return elseif edgeᵢ_orig === edge return end # A distinct CodeInstance for the same MethodInstance may carry a # distinct proof, so retain it as a separate invoke edge. end end i += 1 end push!(edges, atype) push!(edges, edge) nothing end function add_inlining_edge!(edges::Vector{Any}, edge::MethodInstance) # check if we already have an edge to this code i = 1 while i <= length(edges) edgeᵢ = edges[i] if edgeᵢ isa Int i += 2 + abs(edgeᵢ) continue end if edgeᵢ isa Method && edgeᵢ === edge.def # found edge we can upgrade edges[i] = edge return end edgeᵢ isa CodeInstance && (edgeᵢ = edgeᵢ.def) if edgeᵢ isa MethodInstance && edgeᵢ === edge return # found existing covered edge end i += 1 end # add_invoke_edge alone push!(edges, (edge.def::Method).sig) push!(edges, edge) nothing end function add_inlining_edge!(edges::Vector{Any}, edge::CodeInstance) # check if we already have an edge to this code i = 1 while i <= length(edges) edgeᵢ = edges[i] if edgeᵢ isa Int i += 2 + abs(edgeᵢ) continue end if edgeᵢ isa Method && edgeᵢ === edge.def.def # found edge we can upgrade edges[i] = edge return end if edgeᵢ isa MethodInstance && edgeᵢ === edge.def # found edge we can upgrade edges[i] = edge return end if edgeᵢ === edge # found the identical existing edge return end # A distinct CodeInstance for the same MethodInstance may carry a # distinct proof, so do not deduplicate it by `def` alone. i += 1 end # add_invoke_edge alone push!(edges, (get_ci_mi(edge).def::Method).sig) push!(edges, edge) nothing end nsplit_impl(::InvokeCallInfo) = 1 getsplit_impl(info::InvokeCallInfo, idx::Int) = (@assert idx == 1; MethodLookupResult(Core.MethodMatch[info.match], WorldRange(typemin(UInt), typemax(UInt)), false)) getresult_impl(info::InvokeCallInfo, idx::Int) = (@assert idx == 1; info.result) getedge_impl(info::InvokeCallInfo, idx::Int) = (@assert idx == 1; info.edge) """ info::OpaqueClosureCallInfo Represents a resolved call of opaque closure, carrying the `info.match::MethodMatch` of the method that has been processed. Optionally keeps a proof-carrying `info.result` with constant information. """ struct OpaqueClosureCallInfo <: CallInfo edge::Union{Nothing,CodeInstance} match::MethodMatch result::Union{Nothing,InferredCallResult} needs_mi_edge::Bool # targetless body-derived facts require an invalidation target end OpaqueClosureCallInfo(edge::Union{Nothing,CodeInstance}, match::MethodMatch, result::Union{Nothing,InferredCallResult}) = OpaqueClosureCallInfo(edge, match, result, false) function add_edges_impl(edges::Vector{Any}, info::OpaqueClosureCallInfo) edge = info.edge if edge === nothing && (info.needs_mi_edge || info.result !== nothing) edge = specialize_method(info.match) end if edge !== nothing add_one_edge!(edges, edge) end add_result_proof!(edges, info.result, edge) nothing end getedge_impl(info::OpaqueClosureCallInfo, idx::Int) = (@assert idx == 1; info.edge) """ info::OpaqueClosureCreateInfo <: CallInfo This info may be constructed upon opaque closure construction, with `info.unspec::CallMeta` carrying out inference result of an unreal, partially specialized call (i.e. specialized on the closure environment, but not on the argument types of the opaque closure) in order to allow the optimizer to rewrite the return type parameter of the `OpaqueClosure` based on it. """ struct OpaqueClosureCreateInfo <: CallInfo unspec::CallMeta function OpaqueClosureCreateInfo(unspec::CallMeta) @assert isa(unspec.info, Union{OpaqueClosureCallInfo, NoCallInfo}) return new(unspec) end end # merely creating the object implies edges for OC, unlike normal objects, # since calling them doesn't normally have edges in contrast add_edges_impl(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = add_edges!(edges, info.unspec.info) # Stmt infos that are used by external consumers, but not by optimization. # These are not produced by default and must be explicitly opted into by # the AbstractInterpreter. """ info::ReturnTypeCallInfo <: CallInfo Represents a resolved call of `Core.Compiler.return_type`. `info.call` wraps the info corresponding to the call that `Core.Compiler.return_type` call was supposed to analyze. """ struct ReturnTypeCallInfo <: CallInfo info::CallInfo end add_edges_impl(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) """ info::FinalizerInfo <: CallInfo Represents the information of a potential (later) call to the finalizer on the given object type. """ struct FinalizerInfo <: CallInfo info::CallInfo # the callinfo for the finalizer call effects::Effects # the effects for the finalizer call end # merely allocating a finalizer does not imply edges (unless it gets inlined later) add_edges_impl(::Vector{Any}, ::FinalizerInfo) = nothing """ info::ModifyOpInfo <: CallInfo Represents a resolved call of one of: - `modifyfield!(obj, name, op, x, [order])` - `modifyglobal!(mod, var, op, x, order)` - `memoryrefmodify!(memref, op, x, order, boundscheck)` - `Intrinsics.atomic_pointermodify(ptr, op, x, order)` `info.info` wraps the call information of `op(getval(), x)`. """ struct ModifyOpInfo <: CallInfo info::CallInfo # the callinfo for the `op(getval(), x)` call end add_edges_impl(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.info) """ info::TaskCallInfo <: CallInfo Represents a resolved call of `Core._task(f, size)`. `info.info` wraps the call information of the deferred `f()` call that runs when the created task is scheduled. """ struct TaskCallInfo <: CallInfo info::CallInfo # the callinfo for the deferred `f()` call end add_edges_impl(edges::Vector{Any}, info::TaskCallInfo) = add_edges!(edges, info.info) struct VirtualMethodMatchInfo <: CallInfo info::Union{MethodMatchInfo,UnionSplitInfo,InvokeCallInfo} end add_edges_impl(edges::Vector{Any}, info::VirtualMethodMatchInfo) = _add_edges_impl(edges, info.info, #=mi_edge=#true) nsplit_impl(info::VirtualMethodMatchInfo) = nsplit(info.info) getsplit_impl(info::VirtualMethodMatchInfo, idx::Int) = getsplit(info.info, idx) getresult_impl(info::VirtualMethodMatchInfo, idx::Int) = getresult(info.info, idx) getedge_impl(info::VirtualMethodMatchInfo, idx::Int) = getedge(info.info, idx) """ info::GlobalAccessInfo <: CallInfo Represents access to a global through runtime reflection, rather than as a manifest `GlobalRef` in the source code. Used for builtins (getglobal/setglobal/etc.) that perform such accesses. """ struct GlobalAccessInfo <: CallInfo b::Core.Binding end function add_edges_impl(edges::Vector{Any}, info::GlobalAccessInfo) push!(edges, info.b) end @specialize