/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Compiler/src/utilities.jl
415 строк
13 KB
Keno Fischer
type system: Add TypeEgal kind (#62001)
04 июл 2026, 23:05
Не верифицирован
04 июл 2026, 23:05
e7fe47b
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license ########### # generic # ########### if !@isdefined(var"@zone") # This is designed to allow inserting timers when loading a second copy # of inference for performing performance experiments. macro timeit(args...) esc(args[end]) end end function contains_is(itr, @nospecialize(x)) for y in itr if y === x return true end end return false end anymap(f::Function, a::Vector{Any}) = Any[ f(a[i]) for i in 1:length(a) ] ############ # inlining # ############ const MAX_INLINE_CONST_SIZE = 256 function count_const_size(@nospecialize(x), count_self::Bool = true) (x isa Type || x isa Core.TypeName || x isa Symbol) && return 0 if ismutable(x) # No definite size (isa(x, GenericMemory) || isa(x, String) || isa(x, SimpleVector)) && return MAX_INLINE_CONST_SIZE + 1 if isa(x, Module) || isa(x, Method) || isa(x, CodeInstance) # We allow modules, methods and CodeInstance, because we already assume they are externally # rooted, so we count their contents as 0 size. return sizeof(Ptr{Cvoid}) end # We allow mutable types with no mutable fields (i.e. those mutable # types used for identity only). The intent of this function is to # prevent the rooting of large amounts of data that may have been # speculatively computed. If the struct can get mutated later, we # cannot assess how much data we might end up rooting. However, if # the struct is mutable only for identity, the query still works. for i = 1:nfields(x) if !isconst(typeof(x), i) return MAX_INLINE_CONST_SIZE + 1 end end end isbits(x) && return Core.sizeof(x) dt = typeof(x) sz = count_self ? sizeof(dt) : 0 sz > MAX_INLINE_CONST_SIZE && return MAX_INLINE_CONST_SIZE + 1 dtfd = DataTypeFieldDesc(dt) for i = 1:Int(datatype_nfields(dt)) isdefined(x, i) || continue f = getfield(x, i) if !dtfd[i].isptr && datatype_pointerfree(typeof(f)) continue end sz += count_const_size(f, dtfd[i].isptr) sz > MAX_INLINE_CONST_SIZE && return MAX_INLINE_CONST_SIZE + 1 end return sz end function is_inlineable_constant(@nospecialize(x)) return count_const_size(x) <= MAX_INLINE_CONST_SIZE end ########################### # MethodInstance/CodeInfo # ########################### invoke_api(li::CodeInstance) = ccall(:jl_invoke_api, Cint, (Any,), li) use_const_api(li::CodeInstance) = invoke_api(li) == 2 function get_staged(mi::MethodInstance, world::UInt) may_invoke_generator(mi) || return nothing cache_ci = (mi.def::Method).generator isa Core.CachedGenerator ? RefValue{CodeInstance}() : nothing try return call_get_staged(mi, world, cache_ci) catch # user code might throw errors – ignore them return nothing end end # enable caching of unoptimized generated code if the generator is `CachedGenerator` function call_get_staged(mi::MethodInstance, world::UInt, cache_ci::RefValue{CodeInstance}) token = @_gc_preserve_begin cache_ci cache_ci_ptr = pointer_from_objref(cache_ci) src = ccall(:jl_code_for_staged, Ref{CodeInfo}, (Any, UInt, Ptr{CodeInstance}), mi, world, cache_ci_ptr) @_gc_preserve_end token return src end function call_get_staged(mi::MethodInstance, world::UInt, ::Nothing) return ccall(:jl_code_for_staged, Ref{CodeInfo}, (Any, UInt, Ptr{Cvoid}), mi, world, C_NULL) end function get_cached_uninferred(mi::MethodInstance, world::UInt) ccall(:jl_cached_uninferred, Any, (Any, UInt), mi.cache, world)::CodeInstance end function retrieve_code_info(mi::MethodInstance, world::UInt) def = mi.def if !isa(def, Method) ci = get_cached_uninferred(mi, world) src = ci.inferred # Inference may corrupt the src, which is fine, because this is a # (short-lived) top-level thunk, but set it to NULL anyway, so we # can catch it if somebody tries to read it again by accident. # @atomic ci.inferred = C_NULL return src end c = hasgenerator(def) ? get_staged(mi, world) : nothing if c === nothing && isdefined(def, :source) src = def.source if src === nothing # can happen in images built with --strip-ir return nothing elseif isa(src, String) c = ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Ptr{Cvoid}, Any), def, C_NULL, src) else c = copy(src::CodeInfo) end if (def.did_scan_source & 0x1) == 0x0 # This scan must happen: # 1. After method definition # 2. Before any code instances that may have relied on information # from implicit GlobalRefs for this method are added to the cache # 3. Preferably while the IR is already uncompressed # 4. As late as possible, as early adding of the backedges may cause # spurious invalidations. # # At the moment we do so here, because # 1. It's reasonably late # 2. It has easy access to the uncompressed IR # 3. We necessarily pass through here before relying on any # information obtained from implicit GlobalRefs. # # However, the exact placement of this scan is not as important as # long as the above conditions are met. ccall(:jl_scan_method_source_now, Cvoid, (Any, Any), def, c) end end if c isa CodeInfo c.parent = mi return c end return nothing end function get_compileable_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) isa(atype, DataType) || return nothing return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Cint), atype, sparams, method, #=int return_if_compileable=#1) end isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) isa_compileable_sig(m::MethodInstance) = (def = m.def; !isa(def, Method) || isa_compileable_sig(m.specTypes, m.sparam_vals, def)) isa_compileable_sig(::ABIOverride) = false """ is_declared_inline(method::Method)::Bool Check if `method` is declared as `@inline`. """ is_declared_inline(method::Method) = _is_declared_inline(method, true) """ is_declared_noinline(method::Method)::Bool Check if `method` is declared as `@noinline`. """ is_declared_noinline(method::Method) = _is_declared_inline(method, false) function _is_declared_inline(method::Method, inline::Bool) isdefined(method, :source) || return false src = method.source isa(src, MaybeCompressed) || return false return (inline ? is_declared_inline : is_declared_noinline)(src) end """ is_aggressive_constprop(method::Union{Method,CodeInfo})::Bool Check if `method` is declared as `Base.@constprop :aggressive`. """ is_aggressive_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x01 """ is_no_constprop(method::Union{Method,CodeInfo})::Bool Check if `method` is declared as `Base.@constprop :none`. """ is_no_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x02 ######### # types # ######### @nospecializeinfer function singleton_type(@nospecialize(ft)) ft = widenslotwrapper(ft) if isa(ft, Const) return ft.val elseif isconstType(ft) return type_parameter(ft) elseif issingletontype(ft) return ft.instance end return nothing end @nospecializeinfer function maybe_singleton_const(@nospecialize(t)) if isa(t, DataType) && issingletontype(t) return Const(t.instance) elseif isconstType(t) return Const(type_parameter(t)) end return t end ################### # SSAValues/Slots # ################### function ssamap(f, @nospecialize(stmt)) urs = userefs(stmt) for op in urs val = op[] if isa(val, SSAValue) op[] = f(val) end end return urs[] end function foreachssa(@specialize(f), @nospecialize(stmt)) urs = userefs(stmt) for op in urs val = op[] if isa(val, SSAValue) f(val) end end end function foreach_anyssa(@specialize(f), @nospecialize(stmt)) urs = userefs(stmt) for op in urs val = op[] if isa(val, AnySSAValue) f(val) end end end # Uses of each SSA value, in compressed-sparse-row form: the uses of ssa `i` are # `data[offsets[i]:offsets[i+1]-1]`. Entries are use occurrences, not distinct # statement indices, so a statement using the same value twice lists its line # twice; harmless for the consumers (`isempty` and idempotent block re-enqueue). struct SSAUses offsets::Vector{Int} data::Vector{Int} end # Non-escaping view over one value's uses; supports `isempty` and iteration. struct SSAUseList data::Vector{Int} start::Int stop::Int end @inline function getindex(uses::SSAUses, i::Int) offsets = uses.offsets return SSAUseList(uses.data, offsets[i], offsets[i+1] - 1) end @inline isempty(l::SSAUseList) = l.stop < l.start @inline length(l::SSAUseList) = l.stop - l.start + 1 @inline function iterate(l::SSAUseList, i::Int = l.start) i > l.stop && return nothing return (l.data[i], i + 1) end function find_ssavalue_uses(body::Vector{Any}, nvals::Int) # count uses per value counts = zeros(Int, nvals) foreach_ssavalue_use(body) do id::Int, _line::Int counts[id] += 1 end # prefix-sum the counts into row pointers offsets = Vector{Int}(undef, nvals + 1) tot = 1 for i = 1:nvals offsets[i] = tot tot += counts[i] end offsets[nvals + 1] = tot # scatter line numbers into `data`, reusing `counts` as write cursors data = Vector{Int}(undef, tot - 1) fill!(counts, 0) foreach_ssavalue_use(body) do id::Int, line::Int data[offsets[id] + counts[id]] = line counts[id] += 1 end return SSAUses(offsets, data) end # Call `f(ssa_id, line)` for each SSA value used as an operand in `body`. function foreach_ssavalue_use(@specialize(f), body::Vector{Any}) for line in 1:length(body) e = body[line] if isa(e, ReturnNode) isdefined(e, :val) || continue e = e.val elseif isa(e, GotoIfNot) e = e.cond end if isa(e, SSAValue) f(e.id, line) elseif isa(e, Expr) foreach_ssavalue_use(f, e, line) elseif isa(e, PhiNode) foreach_ssavalue_use(f, e, line) end end return nothing end function foreach_ssavalue_use(@specialize(f), e::Expr, line::Int) head = e.head is_meta_expr_head(head) && return skiparg = (head === :(=)) for a in e.args if skiparg skiparg = false elseif isa(a, SSAValue) f(a.id, line) elseif isa(a, Expr) foreach_ssavalue_use(f, a, line) end end return nothing end function foreach_ssavalue_use(@specialize(f), e::PhiNode, line::Int) values = e.values for i = 1:length(values) isassigned(values, i) || continue val = values[i] if isa(val, SSAValue) f(val.id, line) end end return nothing end # using a function to ensure we can infer this @inline function slot_id(s) isa(s, SlotNumber) && return s.id return (s::Argument).n end ########### # options # ########### inlining_enabled() = (JLOptions().can_inline == 1) function instrumentation_enabled(m::Module, only_if_affects_optimizer::Bool) generating_output() && return false # don't alter caches cov = JLOptions().code_coverage if cov == 1 # user m = moduleroot(m) m === Core && return false isdefined(Main, :Base) && m === Main.Base && return false return true elseif cov == 2 # all return true end if !only_if_affects_optimizer log = JLOptions().malloc_log if log == 1 # user m = moduleroot(m) m === Core && return false isdefined(Main, :Base) && m === Main.Base && return false return true elseif log == 2 # all return true end end return false end function inbounds_option() opt_check_bounds = JLOptions().check_bounds opt_check_bounds == 0 && return :default opt_check_bounds == 1 && return :on return :off end is_asserts() = ccall(:jl_is_assertsbuild, Cint, ()) == 1 _time_ns() = ccall(:jl_hrtime, UInt64, ())