diff --git a/base/Base.jl b/base/Base.jl index 84e10ca788ba2e..4d41fc66a9fa71 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -174,6 +174,7 @@ include("ctypes.jl") include("gcutils.jl") include("generator.jl") include("reflection.jl") +include("reflection2.jl") include("options.jl") # define invoke(f, T, args...; kwargs...), without kwargs wrapping diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 577452a873b5ec..4af8fed0e40c39 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -202,8 +202,6 @@ function typesubtract(@nospecialize(a), @nospecialize(b), max_union_splitting::I return a # TODO: improve this bound? end -hasintersect(@nospecialize(a), @nospecialize(b)) = typeintersect(a, b) !== Bottom - _typename(@nospecialize a) = Union{} _typename(a::TypeVar) = Core.TypeName function _typename(a::Union) diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index b3dfd73d534523..f9202788b63605 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -48,24 +48,6 @@ anymap(f::Function, a::Array{Any,1}) = Any[ f(a[i]) for i in 1:length(a) ] _topmod(m::Module) = ccall(:jl_base_relative_to, Any, (Any,), m)::Module -####### -# AST # -####### - -# Meta expression head, these generally can't be deleted even when they are -# in a dead branch but can be ignored when analyzing uses/liveness. -is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo -is_meta_expr(@nospecialize x) = isa(x, Expr) && is_meta_expr_head(x.head) - -function is_self_quoting(@nospecialize(x)) - return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type) || - isa(x,Char) || x === nothing || isa(x,Function) -end - -function quoted(@nospecialize(x)) - return is_self_quoting(x) ? x : QuoteNode(x) -end - ############ # inlining # ############ @@ -116,10 +98,6 @@ function is_inlineable_constant(@nospecialize(x)) return count_const_size(x) <= MAX_INLINE_CONST_SIZE end -is_nospecialized(method::Method) = method.nospecialize ≠ 0 - -is_nospecializeinfer(method::Method) = method.nospecializeinfer && is_nospecialized(method) - ########################### # MethodInstance/CodeInfo # ########################### @@ -192,74 +170,12 @@ function get_compileable_sig(method::Method, @nospecialize(atype), sparams::Simp mt, atype, sparams, method, #=int return_if_compileable=#1) end -function get_nospecializeinfer_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) - isa(atype, DataType) || return method.sig - mt = ccall(:jl_method_get_table, Any, (Any,), method) - mt === nothing && return method.sig - return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any, Cint), - mt, atype, sparams, method, #=int return_if_compileable=#0) -end isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) -# eliminate UnionAll vars that might be degenerate due to having identical bounds, -# or a concrete upper bound and appearing covariantly. -function subst_trivial_bounds(@nospecialize(atype)) - if !isa(atype, UnionAll) - return atype - end - v = atype.var - if isconcretetype(v.ub) || v.lb === v.ub - subst = try - atype{v.ub} - catch - # Note in rare cases a var bound might not be valid to substitute. - nothing - end - if subst !== nothing - return subst_trivial_bounds(subst) - end - end - return UnionAll(v, subst_trivial_bounds(atype.body)) -end - has_typevar(@nospecialize(t), v::TypeVar) = ccall(:jl_has_typevar, Cint, (Any, Any), t, v) != 0 -# If removing trivial vars from atype results in an equivalent type, use that -# instead. Otherwise we can get a case like issue #38888, where a signature like -# f(x::S) where S<:Int -# gets cached and matches a concrete dispatch case. -function normalize_typevars(method::Method, @nospecialize(atype), sparams::SimpleVector) - at2 = subst_trivial_bounds(atype) - if at2 !== atype && at2 == atype - atype = at2 - sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), at2, method.sig)::SimpleVector - sparams = sp_[2]::SimpleVector - end - return Pair{Any,SimpleVector}(atype, sparams) -end - -# get a handle to the unique specialization object representing a particular instantiation of a call -@inline function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false) - if isa(atype, UnionAll) - atype, sparams = normalize_typevars(method, atype, sparams) - end - if is_nospecializeinfer(method) - atype = get_nospecializeinfer_sig(method, atype, sparams) - end - if preexisting - # check cached specializations - # for an existing result stored there - return ccall(:jl_specializations_lookup, Any, (Any, Any), method, atype)::Union{Nothing,MethodInstance} - end - return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atype, sparams) -end - -function specialize_method(match::MethodMatch; kwargs...) - return specialize_method(match.method, match.spec_types, match.sparams; kwargs...) -end - """ is_declared_inline(method::Method) -> Bool diff --git a/base/expr.jl b/base/expr.jl index 478ccd7d7cc20f..723b6b5b636c85 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1560,3 +1560,17 @@ function make_atomiconce(success_order, fail_order, ex) end error("@atomiconce expression missing field access or indexing") end + +# Meta expression head, these generally can't be deleted even when they are +# in a dead branch but can be ignored when analyzing uses/liveness. +is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo +is_meta_expr(@nospecialize x) = isa(x, Expr) && is_meta_expr_head(x.head) + +function is_self_quoting(@nospecialize(x)) + return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type) || + isa(x,Char) || x === nothing || isa(x,Function) +end + +function quoted(@nospecialize(x)) + return is_self_quoting(x) ? x : QuoteNode(x) +end diff --git a/base/reflection.jl b/base/reflection.jl index 80eeb4c4efb12e..9f8db419cf5641 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1181,86 +1181,92 @@ function signature_type(@nospecialize(f), @nospecialize(argtypes)) return rewrap_unionall(Tuple{ft, u.parameters...}, argtypes) end +function get_methodtable(m::Method) + mt = ccall(:jl_method_get_table, Any, (Any,), m) + if mt === nothing + return nothing + end + return mt::Core.MethodTable +end + """ - code_lowered(f, types; generated=true, debuginfo=:default) + has_bottom_parameter(t) -> Bool -Return an array of the lowered forms (IR) for the methods matching the given generic function -and type signature. +Determine whether `t` is a Type for which one or more of its parameters is `Union{}`. +""" +function has_bottom_parameter(t::DataType) + for p in t.parameters + has_bottom_parameter(p) && return true + end + return false +end +has_bottom_parameter(t::typeof(Bottom)) = true +has_bottom_parameter(t::UnionAll) = has_bottom_parameter(unwrap_unionall(t)) +has_bottom_parameter(t::Union) = has_bottom_parameter(t.a) & has_bottom_parameter(t.b) +has_bottom_parameter(t::TypeVar) = has_bottom_parameter(t.ub) +has_bottom_parameter(::Any) = false -If `generated` is `false`, the returned `CodeInfo` instances will correspond to fallback -implementations. An error is thrown if no fallback implementation exists. -If `generated` is `true`, these `CodeInfo` instances will correspond to the method bodies -yielded by expanding the generators. +min_world(m::Core.CodeInstance) = m.min_world +max_world(m::Core.CodeInstance) = m.max_world +min_world(m::Core.CodeInfo) = m.min_world +max_world(m::Core.CodeInfo) = m.max_world -The keyword `debuginfo` controls the amount of code metadata present in the output. +""" + get_world_counter() -Note that an error will be thrown if `types` are not leaf types when `generated` is -`true` and any of the corresponding methods are an `@generated` method. +Returns the current maximum world-age counter. This counter is global and monotonically +increasing. """ -function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool=true, debuginfo::Symbol=:default) - if @isdefined(IRShow) - debuginfo = IRShow.debuginfo(debuginfo) - elseif debuginfo === :default - debuginfo = :source - end - if debuginfo !== :source && debuginfo !== :none - throw(ArgumentError("'debuginfo' must be either :source or :none")) - end - world = get_world_counter() - world == typemax(UInt) && error("code reflection cannot be used from generated functions") - ret = CodeInfo[] - for m in method_instances(f, t, world) - if generated && hasgenerator(m) - if may_invoke_generator(m) - code = ccall(:jl_code_for_staged, Ref{CodeInfo}, (Any, UInt, Ptr{Cvoid}), m, world, C_NULL) - else - error("Could not expand generator for `@generated` method ", m, ". ", - "This can happen if the provided argument types (", t, ") are ", - "not leaf types, but the `generated` argument is `true`.") - end - else - code = uncompressed_ir(m.def::Method) - debuginfo === :none && remove_linenums!(code) - end - push!(ret, code) - end - return ret -end +get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) -hasgenerator(m::Method) = isdefined(m, :generator) -hasgenerator(m::Core.MethodInstance) = hasgenerator(m.def::Method) +""" + tls_world_age() -# low-level method lookup functions used by the compiler +Returns the world the [current_task()](@ref) is executing within. +""" +tls_world_age() = ccall(:jl_get_tls_world_age, UInt, ()) -unionlen(@nospecialize(x)) = x isa Union ? unionlen(x.a) + unionlen(x.b) : 1 +""" + propertynames(x, private=false) -function _uniontypes(@nospecialize(x), ts::Array{Any,1}) - if x isa Union - _uniontypes(x.a, ts) - _uniontypes(x.b, ts) - else - push!(ts, x) - end - return ts -end -uniontypes(@nospecialize(x)) = _uniontypes(x, Any[]) +Get a tuple or a vector of the properties (`x.property`) of an object `x`. +This is typically the same as [`fieldnames(typeof(x))`](@ref), but types +that overload [`getproperty`](@ref) should generally overload `propertynames` +as well to get the properties of an instance of the type. -function _methods(@nospecialize(f), @nospecialize(t), lim::Int, world::UInt) - tt = signature_type(f, t) - return _methods_by_ftype(tt, lim, world) -end +`propertynames(x)` may return only "public" property names that are part +of the documented interface of `x`. If you want it to also return "private" +property names intended for internal use, pass `true` for the optional second argument. +REPL tab completion on `x.` shows only the `private=false` properties. -function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt) - return _methods_by_ftype(t, nothing, lim, world) -end -function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt) - return _methods_by_ftype(t, mt, lim, world, false, RefValue{UInt}(typemin(UInt)), RefValue{UInt}(typemax(UInt)), Ptr{Int32}(C_NULL)) -end -function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt, ambig::Bool, min::Ref{UInt}, max::Ref{UInt}, has_ambig::Ref{Int32}) - return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Vector{Any},Nothing} +See also: [`hasproperty`](@ref), [`hasfield`](@ref). +""" +propertynames(x) = fieldnames(typeof(x)) +propertynames(m::Module) = names(m) +propertynames(x, private::Bool) = propertynames(x) # ignore private flag by default +propertynames(x::Array) = () # hide the fields from tab completion to discourage calling `x.size` instead of `size(x)`, even though they are equivalent + +""" + hasproperty(x, s::Symbol) + +Return a boolean indicating whether the object `x` has `s` as one of its own properties. + +!!! compat "Julia 1.2" + This function requires at least Julia 1.2. + +See also: [`propertynames`](@ref), [`hasfield`](@ref). +""" +hasproperty(x, s::Symbol) = s in propertynames(x) + +""" + delete_method(m::Method) + +Make method `m` uncallable and force recompilation of any methods that use(d) it. +""" +function delete_method(m::Method) + ccall(:jl_method_table_disable, Cvoid, (Any, Any), get_methodtable(m), m) end -# high-level, more convenient method lookup functions # type for reflecting and pretty-printing a subset of methods mutable struct MethodList <: AbstractArray{Method,1} @@ -1323,240 +1329,57 @@ function methods(@nospecialize(f), return methods(f, Tuple{Vararg{Any}}, mod) end -function visit(f, mt::Core.MethodTable) - mt.defs !== nothing && visit(f, mt.defs) - nothing -end -function visit(f, mc::Core.TypeMapLevel) - function avisit(f, e::Memory{Any}) - for i in 2:2:length(e) - isassigned(e, i) || continue - ei = e[i] - if ei isa Memory{Any} - for j in 2:2:length(ei) - isassigned(ei, j) || continue - visit(f, ei[j]) - end - else - visit(f, ei) - end - end - end - if mc.targ !== nothing - avisit(f, mc.targ::Memory{Any}) - end - if mc.arg1 !== nothing - avisit(f, mc.arg1::Memory{Any}) - end - if mc.tname !== nothing - avisit(f, mc.tname::Memory{Any}) - end - if mc.name1 !== nothing - avisit(f, mc.name1::Memory{Any}) - end - mc.list !== nothing && visit(f, mc.list) - mc.any !== nothing && visit(f, mc.any) - nothing -end -function visit(f, d::Core.TypeMapEntry) - while d !== nothing - f(d.func) - d = d.next - end - nothing -end -struct MethodSpecializations - specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} -end -""" - specializations(m::Method) → itr - -Return an iterator `itr` of all compiler-generated specializations of `m`. -""" -specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) -function iterate(specs::MethodSpecializations) - s = specs.specializations - s === nothing && return nothing - isa(s, Core.MethodInstance) && return (s, nothing) - return iterate(specs, 0) -end -iterate(specs::MethodSpecializations, ::Nothing) = nothing -function iterate(specs::MethodSpecializations, i::Int) - s = specs.specializations::Core.SimpleVector - n = length(s) - i >= n && return nothing - item = nothing - while i < n && item === nothing - item = s[i+=1] - end - item === nothing && return nothing - return (item, i) -end -length(specs::MethodSpecializations) = count(Returns(true), specs) +# low-level method lookup functions used by the compiler -function length(mt::Core.MethodTable) - n = 0 - visit(mt) do m - n += 1 - end - return n::Int -end -isempty(mt::Core.MethodTable) = (mt.defs === nothing) +unionlen(@nospecialize(x)) = x isa Union ? unionlen(x.a) + unionlen(x.b) : 1 -uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) : - isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : - error("Code for this Method is not available.") -function _uncompressed_ir(m::Method) - s = m.source - if s isa String - s = ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Ptr{Cvoid}, Any), m, C_NULL, s) +function _uniontypes(@nospecialize(x), ts::Array{Any,1}) + if x isa Union + _uniontypes(x.a, ts) + _uniontypes(x.b, ts) + else + push!(ts, x) end - return s::CodeInfo + return ts end +uniontypes(@nospecialize(x)) = _uniontypes(x, Any[]) -# for backwards compat -const uncompressed_ast = uncompressed_ir -const _uncompressed_ast = _uncompressed_ir - -function method_instances(@nospecialize(f), @nospecialize(t), world::UInt) +function _methods(@nospecialize(f), @nospecialize(t), lim::Int, world::UInt) tt = signature_type(f, t) - results = Core.MethodInstance[] - # this make a better error message than the typeassert that follows - world == typemax(UInt) && error("code reflection cannot be used from generated functions") - for match in _methods_by_ftype(tt, -1, world)::Vector - instance = Core.Compiler.specialize_method(match::Core.MethodMatch) - push!(results, instance) - end - return results + return _methods_by_ftype(tt, lim, world) end -function method_instance(@nospecialize(f), @nospecialize(t); - world=Base.get_world_counter(), method_table=nothing) - tt = signature_type(f, t) - mi = ccall(:jl_method_lookup_by_tt, Any, - (Any, Csize_t, Any), - tt, world, method_table) - return mi::Union{Nothing, MethodInstance} -end - -default_debug_info_kind() = unsafe_load(cglobal(:jl_default_debug_info_kind, Cint)) - -# this type mirrors jl_cgparams_t (documented in julia.h) -struct CodegenParams - """ - If enabled, generate the necessary code to support the --track-allocations - command line flag to julia itself. Note that the option itself does not enable - allocation tracking. Rather, it merely generates the support code necessary - to perform allocation tracking if requested by the command line option. - """ - track_allocations::Cint - - """ - If enabled, generate the necessary code to support the --code-coverage - command line flag to julia itself. Note that the option itself does not enable - code coverage. Rather, it merely generates the support code necessary - to code coverage if requested by the command line option. - """ - code_coverage::Cint - - """ - If enabled, force the compiler to use the specialized signature - for all generated functions, whenever legal. If disabled, the choice is made - heuristically and specsig is only used when deemed profitable. - """ - prefer_specsig::Cint - - """ - If enabled, enable emission of `.debug_names` sections. - """ - gnu_pubnames::Cint - - """ - Controls what level of debug info to emit. Currently supported values are: - - 0: no debug info - - 1: full debug info - - 2: Line tables only - - 3: Debug directives only - - The integer values currently match the llvm::DICompilerUnit::DebugEmissionKind enum, - although this is not guaranteed. - """ - debug_info_kind::Cint - - """ - Controls the debug_info_level parameter, equivalent to the -g command line option. - """ - debug_info_level::Cint - - """ - If enabled, generate a GC safepoint at the entry to every function. Emitting - these extra safepoints can reduce the amount of time that other threads are - waiting for the currently running thread to reach a safepoint. The cost for - a safepoint is small, but non-zero. The option is enabled by default. - """ - safepoint_on_entry::Cint - - """ - If enabled, add an implicit argument to each function call that is used to - pass down the current task local state pointer. This argument is passed - using the `swiftself` convention, which in the ordinary case means that the - pointer is kept in a register and accesses are thus very fast. If this option - is disabled, the task local state pointer must be loaded from thread local - storage, which incurs a small amount of additional overhead. The option is enabled by - default. - """ - gcstack_arg::Cint - - """ - If enabled, use the Julia PLT mechanism to support lazy-resolution of `ccall` - targets. The option may be disabled for use in environments where the julia - runtime is unavailable, but is otherwise recommended to be enabled, even if - lazy resolution is not required, as the Julia PLT mechanism may have superior - performance compared to the native platform mechanism. The options is enabled by default. - """ - use_jlplt::Cint - - """ - If enabled, only provably reachable code (from functions marked with `entrypoint`) is included - in the output system image. Errors or warnings can be given for call sites too dynamic to handle. - The option is disabled by default. (0=>disabled, 1=>safe (static errors), 2=>unsafe, 3=>unsafe plus warnings) - """ - trim::Cint - - """ - A pointer of type - - typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, - size_t min_world, size_t max_world); - - that may be used by external compilers as a callback to look up the code instance corresponding - to a particular method instance. - """ - lookup::Ptr{Cvoid} - - function CodegenParams(; track_allocations::Bool=true, code_coverage::Bool=true, - prefer_specsig::Bool=false, - gnu_pubnames::Bool=true, debug_info_kind::Cint = default_debug_info_kind(), - debug_info_level::Cint = Cint(JLOptions().debug_level), safepoint_on_entry::Bool=true, - gcstack_arg::Bool=true, use_jlplt::Bool=true, trim::Cint=Cint(0), - lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid}))) - return new( - Cint(track_allocations), Cint(code_coverage), - Cint(prefer_specsig), - Cint(gnu_pubnames), debug_info_kind, - debug_info_level, Cint(safepoint_on_entry), - Cint(gcstack_arg), Cint(use_jlplt), Cint(trim), - lookup) - end +function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt) + return _methods_by_ftype(t, nothing, lim, world) +end +function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt) + return _methods_by_ftype(t, mt, lim, world, false, RefValue{UInt}(typemin(UInt)), RefValue{UInt}(typemax(UInt)), Ptr{Int32}(C_NULL)) +end +function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt, ambig::Bool, min::Ref{UInt}, max::Ref{UInt}, has_ambig::Ref{Int32}) + return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Vector{Any},Nothing} end -# this type mirrors jl_emission_params_t (documented in julia.h) -struct EmissionParams - emit_metadata::Cint +hasgenerator(m::Method) = isdefined(m, :generator) +hasgenerator(m::Core.MethodInstance) = hasgenerator(m.def::Method) + +""" + Base.generating_output([incremental::Bool])::Bool + +Return `true` if the current process is being used to pre-generate a +code cache via any of the `--output-*` command line arguments. The optional +`incremental` argument further specifies the precompilation mode: when set +to `true`, the function will return `true` only for package precompilation; +when set to `false`, it will return `true` only for system image generation. - function EmissionParams(; emit_metadata::Bool=true) - return new(Cint(emit_metadata)) +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. +""" +function generating_output(incremental::Union{Bool,Nothing}=nothing) + ccall(:jl_generating_output, Cint, ()) == 0 && return false + if incremental !== nothing + JLOptions().incremental == incremental || return false end + return true end const SLOT_USED = 0x8 @@ -1637,1287 +1460,70 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim return true end -""" - code_typed(f, types; kw...) - -Returns an array of type-inferred lowered form (IR) for the methods matching the given -generic function and type signature. - -# Keyword Arguments - -- `optimize::Bool = true`: optional, controls whether additional optimizations, - such as inlining, are also applied. -- `debuginfo::Symbol = :default`: optional, controls the amount of code metadata present - in the output, possible options are `:source` or `:none`. - -# Internal Keyword Arguments - -This section should be considered internal, and is only for who understands Julia compiler -internals. - -- `world::UInt = Base.get_world_counter()`: optional, controls the world age to use - when looking up methods, use current world age if not specified. -- `interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world)`: - optional, controls the abstract interpreter to use, use the native interpreter if not specified. - -# Examples - -One can put the argument types in a tuple to get the corresponding `code_typed`. - -```julia -julia> code_typed(+, (Float64, Float64)) -1-element Vector{Any}: - CodeInfo( -1 ─ %1 = Base.add_float(x, y)::Float64 -└── return %1 -) => Float64 -``` -""" -function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); kwargs...) - if isa(f, Core.OpaqueClosure) - return code_typed_opaque_closure(f, types; kwargs...) - end - tt = signature_type(f, types) - return code_typed_by_type(tt; kwargs...) -end - -# returns argument tuple type which is supposed to be used for `code_typed` and its family; -# if there is a single method this functions returns the method argument signature, -# otherwise returns `Tuple` that doesn't match with any signature -function default_tt(@nospecialize(f)) - ms = methods(f) - if length(ms) == 1 - return tuple_type_tail(only(ms).sig) - else - return Tuple - end -end - -function raise_match_failure(name::Symbol, @nospecialize(tt)) - @noinline - sig_str = sprint(Base.show_tuple_as_call, Symbol(""), tt) - error("$name: unanalyzable call given $sig_str") -end - -""" - code_typed_by_type(types::Type{<:Tuple}; ...) - -Similar to [`code_typed`](@ref), except the argument is a tuple type describing -a full signature to query. -""" -function code_typed_by_type(@nospecialize(tt::Type); - optimize::Bool=true, - debuginfo::Symbol=:default, - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") - if @isdefined(IRShow) - debuginfo = IRShow.debuginfo(debuginfo) - elseif debuginfo === :default - debuginfo = :source - end - if debuginfo !== :source && debuginfo !== :none - throw(ArgumentError("'debuginfo' must be either :source or :none")) - end - tt = to_tuple_type(tt) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:code_typed, tt) - asts = [] - for match in matches.matches - match = match::Core.MethodMatch - code = Core.Compiler.typeinf_code(interp, match, optimize) - if code === nothing - push!(asts, match.method => Any) - else - debuginfo === :none && remove_linenums!(code) - push!(asts, code => code.rettype) +# get a handle to the unique specialization object representing a particular instantiation of a call +# eliminate UnionAll vars that might be degenerate due to having identical bounds, +# or a concrete upper bound and appearing covariantly. +function subst_trivial_bounds(@nospecialize(atype)) + if !isa(atype, UnionAll) + return atype + end + v = atype.var + if isconcretetype(v.ub) || v.lb === v.ub + subst = try + atype{v.ub} + catch + # Note in rare cases a var bound might not be valid to substitute. + nothing end - end - return asts -end - -function get_oc_code_rt(oc::Core.OpaqueClosure, types, optimize::Bool) - @nospecialize oc types - ccall(:jl_is_in_pure_context, Bool, ()) && - error("code reflection cannot be used from generated functions") - m = oc.source - if isa(m, Method) - if isdefined(m, :source) - if optimize - tt = Tuple{typeof(oc.captures), to_tuple_type(types).parameters...} - mi = Core.Compiler.specialize_method(m, tt, Core.svec()) - interp = Core.Compiler.NativeInterpreter(m.primary_world) - code = Core.Compiler.typeinf_code(interp, mi, optimize) - if code isa CodeInfo - return Pair{CodeInfo, Any}(code, code.rettype) - end - error("inference not successful") - else - code = _uncompressed_ir(m) - return Pair{CodeInfo, Any}(code, typeof(oc).parameters[2]) - end - else - # OC constructed from optimized IR - codeinst = m.specializations.cache - # XXX: the inferred field is not normally a CodeInfo, but this assumes it is guaranteed to be always - return Pair{CodeInfo, Any}(codeinst.inferred, codeinst.rettype) + if subst !== nothing + return subst_trivial_bounds(subst) end - else - error("encountered invalid Core.OpaqueClosure object") end + return UnionAll(v, subst_trivial_bounds(atype.body)) end -function code_typed_opaque_closure(oc::Core.OpaqueClosure, types; - debuginfo::Symbol=:default, - optimize::Bool=true, - _...) - @nospecialize oc types - (code, rt) = get_oc_code_rt(oc, types, optimize) - debuginfo === :none && remove_linenums!(code) - return Any[Pair{CodeInfo,Any}(code, rt)] -end - -""" - code_ircode(f, [types]) - -Return an array of pairs of `IRCode` and inferred return type if type inference succeeds. -The `Method` is included instead of `IRCode` otherwise. - -See also: [`code_typed`](@ref) - -# Internal Keyword Arguments - -This section should be considered internal, and is only for who understands Julia compiler -internals. - -- `world::UInt = Base.get_world_counter()`: optional, controls the world age to use - when looking up methods, use current world age if not specified. -- `interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world)`: - optional, controls the abstract interpreter to use, use the native interpreter if not specified. -- `optimize_until::Union{Integer,AbstractString,Nothing} = nothing`: optional, - controls the optimization passes to run. - If it is a string, it specifies the name of the pass up to which the optimizer is run. - If it is an integer, it specifies the number of passes to run. - If it is `nothing` (default), all passes are run. - -# Examples - -One can put the argument types in a tuple to get the corresponding `code_ircode`. - -```julia -julia> Base.code_ircode(+, (Float64, Int64)) -1-element Vector{Any}: - 388 1 ─ %1 = Base.sitofp(Float64, _3)::Float64 - │ %2 = Base.add_float(_2, %1)::Float64 - └── return %2 - => Float64 - -julia> Base.code_ircode(+, (Float64, Int64); optimize_until = "compact 1") -1-element Vector{Any}: - 388 1 ─ %1 = Base.promote(_2, _3)::Tuple{Float64, Float64} - │ %2 = Core._apply_iterate(Base.iterate, Base.:+, %1)::Float64 - └── return %2 - => Float64 -``` -""" -function code_ircode(@nospecialize(f), @nospecialize(types = default_tt(f)); kwargs...) - if isa(f, Core.OpaqueClosure) - error("OpaqueClosure not supported") - end - tt = signature_type(f, types) - return code_ircode_by_type(tt; kwargs...) -end - -""" - code_ircode_by_type(types::Type{<:Tuple}; ...) - -Similar to [`code_ircode`](@ref), except the argument is a tuple type describing -a full signature to query. -""" -function code_ircode_by_type( - @nospecialize(tt::Type); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world), - optimize_until::Union{Integer,AbstractString,Nothing}=nothing, -) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") - tt = to_tuple_type(tt) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:code_ircode, tt) - asts = [] - for match in matches.matches - match = match::Core.MethodMatch - (code, ty) = Core.Compiler.typeinf_ircode(interp, match, optimize_until) - if code === nothing - push!(asts, match.method => Any) - else - push!(asts, code => ty) - end +# If removing trivial vars from atype results in an equivalent type, use that +# instead. Otherwise we can get a case like issue #38888, where a signature like +# f(x::S) where S<:Int +# gets cached and matches a concrete dispatch case. +function normalize_typevars(method::Method, @nospecialize(atype), sparams::SimpleVector) + at2 = subst_trivial_bounds(atype) + if at2 !== atype && at2 == atype + atype = at2 + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), at2, method.sig)::SimpleVector + sparams = sp_[2]::SimpleVector end - return asts -end - -function _builtin_return_type(interp::Core.Compiler.AbstractInterpreter, - @nospecialize(f::Core.Builtin), @nospecialize(types)) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.widenconst(rt) + return Pair{Any,SimpleVector}(atype, sparams) end -function _builtin_effects(interp::Core.Compiler.AbstractInterpreter, - @nospecialize(f::Core.Builtin), @nospecialize(types)) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) +function get_nospecializeinfer_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) + isa(atype, DataType) || return method.sig + mt = ccall(:jl_method_get_table, Any, (Any,), method) + mt === nothing && return method.sig + return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any, Cint), + mt, atype, sparams, method, #=int return_if_compileable=#0) end -function _builtin_exception_type(interp::Core.Compiler.AbstractInterpreter, - @nospecialize(f::Core.Builtin), @nospecialize(types)) - effects = _builtin_effects(interp, f, types) - return Core.Compiler.is_nothrow(effects) ? Union{} : Any -end - -check_generated_context(world::UInt) = - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") - -# TODO rename `Base.return_types` to `Base.infer_return_types` - -""" - Base.return_types( - f, types=default_tt(f); - world::UInt=get_world_counter(), - interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} - -Return a list of possible return types for a given function `f` and argument types `types`. -The list corresponds to the results of type inference on all the possible method match -candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). - -# Arguments -- `f`: The function to analyze. -- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. -- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. - -# Returns -- `rts::Vector{Any}`: The list of return types that are figured out by inference on - methods matching with the given `f` and `types`. The list's order matches the order - returned by `methods(f, types)`. - -# Examples - -```julia -julia> Base.return_types(sum, Tuple{Vector{Int}}) -1-element Vector{Any}: - Int64 - -julia> methods(sum, (Union{Vector{Int},UnitRange{Int}},)) -# 2 methods for generic function "sum" from Base: - [1] sum(r::AbstractRange{<:Real}) - @ range.jl:1399 - [2] sum(a::AbstractArray; dims, kw...) - @ reducedim.jl:1010 - -julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) -2-element Vector{Any}: - Int64 # the result of inference on sum(r::AbstractRange{<:Real}) - Int64 # the result of inference on sum(a::AbstractArray; dims, kw...) -``` - -!!! warning - The `Base.return_types` function should not be used from generated functions; - doing so will result in an error. -""" -function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - check_generated_context(world) - if isa(f, Core.OpaqueClosure) - _, rt = only(code_typed_opaque_closure(f, types)) - return Any[rt] - elseif isa(f, Core.Builtin) - return Any[_builtin_return_type(interp, f, types)] +is_nospecialized(method::Method) = method.nospecialize ≠ 0 +is_nospecializeinfer(method::Method) = method.nospecializeinfer && is_nospecialized(method) +function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false) + @inline + if isa(atype, UnionAll) + atype, sparams = normalize_typevars(method, atype, sparams) end - tt = signature_type(f, types) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:return_types, tt) - rts = Any[] - for match in matches.matches - ty = Core.Compiler.typeinf_type(interp, match::Core.MethodMatch) - push!(rts, something(ty, Any)) + if is_nospecializeinfer(method) + atype = get_nospecializeinfer_sig(method, atype, sparams) end - return rts + if preexisting + # check cached specializations + # for an existing result stored there + return ccall(:jl_specializations_lookup, Any, (Any, Any), method, atype)::Union{Nothing,MethodInstance} + end + return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atype, sparams) end -""" - Base.infer_return_type( - f, types=default_tt(f); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> rt::Type - -Returns an inferred return type of the function call specified by `f` and `types`. - -# Arguments -- `f`: The function to analyze. -- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. -- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. - -# Returns -- `rt::Type`: An inferred return type of the function call specified by the given call signature. - -!!! note - Note that, different from [`Base.return_types`](@ref), this doesn't give you the list - return types of every possible method matching with the given `f` and `types`. - It returns a single return type, taking into account all potential outcomes of - any function call entailed by the given signature type. - -# Examples - -```julia -julia> checksym(::Symbol) = :symbol; - -julia> checksym(x::Any) = x; - -julia> Base.infer_return_type(checksym, (Union{Symbol,String},)) -Union{String, Symbol} - -julia> Base.return_types(checksym, (Union{Symbol,String},)) -2-element Vector{Any}: - Symbol - Union{String, Symbol} -``` - -It's important to note the difference here: `Base.return_types` gives back inferred results -for each method that matches the given signature `checksum(::Union{Symbol,String})`. -On the other hand `Base.infer_return_type` returns one collective result that sums up all those possibilities. +function specialize_method(match::Core.MethodMatch; kwargs...) + return specialize_method(match.method, match.spec_types, match.sparams; kwargs...) +end -!!! warning - The `Base.infer_return_type` function should not be used from generated functions; - doing so will result in an error. -""" -function infer_return_type(@nospecialize(f), @nospecialize(types=default_tt(f)); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - check_generated_context(world) - if isa(f, Core.OpaqueClosure) - return last(only(code_typed_opaque_closure(f, types))) - elseif isa(f, Core.Builtin) - return _builtin_return_type(interp, f, types) - end - tt = signature_type(f, types) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:infer_return_type, tt) - rt = Union{} - for match in matches.matches - ty = Core.Compiler.typeinf_type(interp, match::Core.MethodMatch) - rt = Core.Compiler.tmerge(rt, something(ty, Any)) - end - return rt -end - -""" - Base.infer_exception_types( - f, types=default_tt(f); - world::UInt=get_world_counter(), - interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} - -Return a list of possible exception types for a given function `f` and argument types `types`. -The list corresponds to the results of type inference on all the possible method match -candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). -It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. - -# Arguments -- `f`: The function to analyze. -- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. -- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. - -# Returns -- `excts::Vector{Any}`: The list of exception types that are figured out by inference on - methods matching with the given `f` and `types`. The list's order matches the order - returned by `methods(f, types)`. - -# Examples - -```julia -julia> throw_if_number(::Number) = error("number is given"); - -julia> throw_if_number(::Any) = nothing; - -julia> Base.infer_exception_types(throw_if_number, (Int,)) -1-element Vector{Any}: - ErrorException - -julia> methods(throw_if_number, (Any,)) -# 2 methods for generic function "throw_if_number" from Main: - [1] throw_if_number(x::Number) - @ REPL[1]:1 - [2] throw_if_number(::Any) - @ REPL[2]:1 - -julia> Base.infer_exception_types(throw_if_number, (Any,)) -2-element Vector{Any}: - ErrorException # the result of inference on `throw_if_number(::Number)` - Union{} # the result of inference on `throw_if_number(::Any)` -``` - -!!! warning - The `Base.infer_exception_types` function should not be used from generated functions; - doing so will result in an error. -""" -function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - check_generated_context(world) - if isa(f, Core.OpaqueClosure) - return Any[Any] # TODO - elseif isa(f, Core.Builtin) - return Any[_builtin_exception_type(interp, f, types)] - end - tt = signature_type(f, types) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:infer_exception_types, tt) - excts = Any[] - for match in matches.matches - frame = Core.Compiler.typeinf_frame(interp, match::Core.MethodMatch, #=run_optimizer=#false) - if frame === nothing - exct = Any - else - exct = Core.Compiler.widenconst(frame.result.exc_result) - end - push!(excts, exct) - end - return excts -end - -_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = - matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) - -""" - Base.infer_exception_type( - f, types=default_tt(f); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type - -Returns the type of exception potentially thrown by the function call specified by `f` and `types`. - -# Arguments -- `f`: The function to analyze. -- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. -- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. - -# Returns -- `exct::Type`: The inferred type of exception that can be thrown by the function call - specified by the given call signature. - -!!! note - Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list - exception types for every possible matching method with the given `f` and `types`. - It returns a single exception type, taking into account all potential outcomes of - any function call entailed by the given signature type. - -# Examples - -```julia -julia> f1(x) = x * 2; - -julia> Base.infer_exception_type(f1, (Int,)) -Union{} -``` - -The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. - -```julia -julia> f2(x::Int) = x * 2; - -julia> Base.infer_exception_type(f2, (Integer,)) -MethodError -``` - -This case is pretty much the same as with `f1`, but there's a key difference to note. For -`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. -Because of this, taking into account the chance of the method error entailed by the call -signature, the exception type is widened to `MethodError`. - -!!! warning - The `Base.infer_exception_type` function should not be used from generated functions; - doing so will result in an error. -""" -function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - check_generated_context(world) - if isa(f, Core.OpaqueClosure) - return Any # TODO - elseif isa(f, Core.Builtin) - return _builtin_exception_type(interp, f, types) - end - tt = signature_type(f, types) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:infer_exception_type, tt) - exct = Union{} - if _may_throw_methoderror(matches) - # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. - exct = Core.Compiler.tmerge(exct, MethodError) - end - for match in matches.matches - match = match::Core.MethodMatch - frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) - frame === nothing && return Any - exct = Core.Compiler.tmerge(exct, Core.Compiler.widenconst(frame.result.exc_result)) - end - return exct -end - -""" - Base.infer_effects( - f, types=default_tt(f); - optimize::Bool=true, - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects - -Returns the possible computation effects of the function call specified by `f` and `types`. - -# Arguments -- `f`: The function to analyze. -- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. -- `optimize` (optional): Whether to run additional effects refinements based on post-optimization analysis. -- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. - -# Returns -- `effects::Effects`: The computed effects of the function call specified by the given call signature. - See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) - for more information on the various effect properties. - -!!! note - Note that, different from [`Base.return_types`](@ref), this doesn't give you the list - effect analysis results for every possible matching method with the given `f` and `types`. - It returns a single effect, taking into account all potential outcomes of any function - call entailed by the given signature type. - -# Examples - -```julia -julia> f1(x) = x * 2; - -julia> Base.infer_effects(f1, (Int,)) -(+c,+e,+n,+t,+s,+m,+i) -``` - -This function will return an `Effects` object with information about the computational -effects of the function `f1` when called with an `Int` argument. - -```julia -julia> f2(x::Int) = x * 2; - -julia> Base.infer_effects(f2, (Integer,)) -(+c,+e,!n,+t,+s,+m,+i) -``` - -This case is pretty much the same as with `f1`, but there's a key difference to note. For -`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. -Because of this, taking into account the chance of the method error entailed by the call -signature, the `:nothrow` bit gets tainted. - -!!! warning - The `Base.infer_effects` function should not be used from generated functions; - doing so will result in an error. - -# See Also -- [`Core.Compiler.Effects`](@ref): A type representing the computational effects of a method call. -- [`Base.@assume_effects`](@ref): A macro for making assumptions about the effects of a method. -""" -function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); - optimize::Bool=true, - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - check_generated_context(world) - if isa(f, Core.Builtin) - return _builtin_effects(interp, f, types) - end - tt = signature_type(f, types) - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:infer_effects, tt) - effects = Core.Compiler.EFFECTS_TOTAL - if _may_throw_methoderror(matches) - # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. - effects = Core.Compiler.Effects(effects; nothrow=false) - end - for match in matches.matches - match = match::Core.MethodMatch - frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#optimize) - frame === nothing && return Core.Compiler.Effects() - effects = Core.Compiler.merge_effects(effects, frame.result.ipo_effects) - end - return effects -end - -""" - print_statement_costs(io::IO, f, types) - -Print type-inferred and optimized code for `f` given argument types `types`, -prepending each line with its cost as estimated by the compiler's inlining engine. -""" -function print_statement_costs(io::IO, @nospecialize(f), @nospecialize(t); kwargs...) - tt = signature_type(f, t) - print_statement_costs(io, tt; kwargs...) -end - -function print_statement_costs(io::IO, @nospecialize(tt::Type); - world::UInt=get_world_counter(), - interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - tt = to_tuple_type(tt) - world == typemax(UInt) && error("code reflection cannot be used from generated functions") - matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) - matches === nothing && raise_match_failure(:print_statement_costs, tt) - params = Core.Compiler.OptimizationParams(interp) - cst = Int[] - for match in matches.matches - match = match::Core.MethodMatch - println(io, match.method) - code = Core.Compiler.typeinf_code(interp, match, true) - if code === nothing - println(io, " inference not successful") - else - empty!(cst) - resize!(cst, length(code.code)) - sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, false) for sp in match.sparams] - maxcost = Core.Compiler.statement_costs!(cst, code.code, code, sptypes, params) - nd = ndigits(maxcost) - irshow_config = IRShow.IRShowConfig() do io, linestart, idx - print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ") - return "" - end - IRShow.show_ir(io, code, irshow_config) - end - println(io) - end -end - -print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args...; kwargs...) - -function _which(@nospecialize(tt::Type); - method_table::Union{Nothing,Core.MethodTable,Core.Compiler.MethodTableView}=nothing, - world::UInt=get_world_counter(), - raise::Bool=true) - world == typemax(UInt) && error("code reflection cannot be used from generated functions") - if method_table === nothing - table = Core.Compiler.InternalMethodTable(world) - elseif method_table isa Core.MethodTable - table = Core.Compiler.OverlayMethodTable(world, method_table) - else - table = method_table - end - match, = Core.Compiler.findsup(tt, table) - if match === nothing - raise && error("no unique matching method found for the specified argument types") - return nothing - end - return match -end - -""" - which(f, types) - -Returns the method of `f` (a `Method` object) that would be called for arguments of the given `types`. - -If `types` is an abstract type, then the method that would be called by `invoke` is returned. - -See also: [`parentmodule`](@ref), [`@which`](@ref Main.InteractiveUtils.@which), and [`@edit`](@ref Main.InteractiveUtils.@edit). -""" -function which(@nospecialize(f), @nospecialize(t)) - tt = signature_type(f, t) - world = get_world_counter() - match, _ = Core.Compiler._findsup(tt, nothing, world) - if match === nothing - me = MethodError(f, t, world) - ee = ErrorException(sprint(io -> begin - println(io, "Calling invoke(f, t, args...) would throw:"); - Base.showerror(io, me); - end)) - throw(ee) - end - return match.method -end - -""" - which(types::Type{<:Tuple}) - -Returns the method that would be called by the given type signature (as a tuple type). -""" -function which(@nospecialize(tt#=::Type=#)) - return _which(tt).method -end - -""" - which(module, symbol) - -Return the module in which the binding for the variable referenced by `symbol` in `module` was created. -""" -function which(m::Module, s::Symbol) - if !isdefined(m, s) - error("\"$s\" is not defined in module $m") - end - return binding_module(m, s) -end - -# function reflection - -""" - nameof(f::Function) -> Symbol - -Get the name of a generic `Function` as a symbol. For anonymous functions, -this is a compiler-generated name. For explicitly-declared subtypes of -`Function`, it is the name of the function's type. -""" -function nameof(f::Function) - t = typeof(f) - mt = t.name.mt - if mt === Symbol.name.mt - # uses shared method table, so name is not unique to this function type - return nameof(t) - end - return mt.name -end - -function nameof(f::Core.IntrinsicFunction) - name = ccall(:jl_intrinsic_name, Ptr{UInt8}, (Core.IntrinsicFunction,), f) - return ccall(:jl_symbol, Ref{Symbol}, (Ptr{UInt8},), name) -end - -""" - parentmodule(f::Function) -> Module - -Determine the module containing the (first) definition of a generic -function. -""" -parentmodule(f::Function) = parentmodule(typeof(f)) - -""" - parentmodule(f::Function, types) -> Module - -Determine the module containing the first method of a generic function `f` matching -the specified `types`. -""" -function parentmodule(@nospecialize(f), @nospecialize(types)) - m = methods(f, types) - if isempty(m) - error("no matching methods") - end - return parentmodule(first(m)) -end - -""" - parentmodule(m::Method) -> Module - -Return the module in which the given method `m` is defined. - -!!! compat "Julia 1.9" - Passing a `Method` as an argument requires Julia 1.9 or later. -""" -parentmodule(m::Method) = m.module - -""" - hasmethod(f, t::Type{<:Tuple}[, kwnames]; world=get_world_counter()) -> Bool - -Determine whether the given generic function has a method matching the given -`Tuple` of argument types with the upper bound of world age given by `world`. - -If a tuple of keyword argument names `kwnames` is provided, this also checks -whether the method of `f` matching `t` has the given keyword argument names. -If the matching method accepts a variable number of keyword arguments, e.g. -with `kwargs...`, any names given in `kwnames` are considered valid. Otherwise -the provided names must be a subset of the method's keyword arguments. - -See also [`applicable`](@ref). - -!!! compat "Julia 1.2" - Providing keyword argument names requires Julia 1.2 or later. - -# Examples -```jldoctest -julia> hasmethod(length, Tuple{Array}) -true - -julia> f(; oranges=0) = oranges; - -julia> hasmethod(f, Tuple{}, (:oranges,)) -true - -julia> hasmethod(f, Tuple{}, (:apples, :bananas)) -false - -julia> g(; xs...) = 4; - -julia> hasmethod(g, Tuple{}, (:a, :b, :c, :d)) # g accepts arbitrary kwargs -true -``` -""" -function hasmethod(@nospecialize(f), @nospecialize(t)) - return Core._hasmethod(signature_type(f, t)) -end - -function Core.kwcall(kwargs::NamedTuple, ::typeof(hasmethod), @nospecialize(f), @nospecialize(t)) - world = kwargs.world::UInt # make sure this is the only local, to avoid confusing kwarg_decl() - return ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), signature_type(f, t), nothing, world) !== nothing -end - -function hasmethod(f, t, kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_counter()) - @nospecialize - world == typemax(UInt) && error("code reflection cannot be used from generated functions") - isempty(kwnames) && return hasmethod(f, t; world) - t = to_tuple_type(t) - ft = Core.Typeof(f) - u = unwrap_unionall(t)::DataType - tt = rewrap_unionall(Tuple{typeof(Core.kwcall), NamedTuple, ft, u.parameters...}, t) - match = ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tt, nothing, world) - match === nothing && return false - kws = ccall(:jl_uncompress_argnames, Array{Symbol,1}, (Any,), (match::Method).slot_syms) - isempty(kws) && return true # some kwfuncs simply forward everything directly - for kw in kws - endswith(String(kw), "...") && return true - end - kwnames = collect(kwnames) - return issubset(kwnames, kws) -end - -""" - fbody = bodyfunction(basemethod::Method) - -Find the keyword "body function" (the function that contains the body of the method -as written, called after all missing keyword-arguments have been assigned default values). -`basemethod` is the method you obtain via [`which`](@ref) or [`methods`](@ref). -""" -function bodyfunction(basemethod::Method) - fmod = parentmodule(basemethod) - # The lowered code for `basemethod` should look like - # %1 = mkw(kwvalues..., #self#, args...) - # return %1 - # where `mkw` is the name of the "active" keyword body-function. - ast = uncompressed_ast(basemethod) - if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 - callexpr = ast.code[end-1] - if isa(callexpr, Expr) && callexpr.head === :call - fsym = callexpr.args[1] - while true - if isa(fsym, Symbol) - return getfield(fmod, fsym) - elseif isa(fsym, GlobalRef) - if fsym.mod === Core && fsym.name === :_apply - fsym = callexpr.args[2] - elseif fsym.mod === Core && fsym.name === :_apply_iterate - fsym = callexpr.args[3] - end - if isa(fsym, Symbol) - return getfield(fmod, fsym)::Function - elseif isa(fsym, GlobalRef) - return getfield(fsym.mod, fsym.name)::Function - elseif isa(fsym, Core.SSAValue) - fsym = ast.code[fsym.id] - else - return nothing - end - elseif isa(fsym, Core.SSAValue) - fsym = ast.code[fsym.id] - else - return nothing - end - end - end - end - return nothing -end - -""" - Base.isambiguous(m1, m2; ambiguous_bottom=false) -> Bool - -Determine whether two methods `m1` and `m2` may be ambiguous for some call -signature. This test is performed in the context of other methods of the same -function; in isolation, `m1` and `m2` might be ambiguous, but if a third method -resolving the ambiguity has been defined, this returns `false`. -Alternatively, in isolation `m1` and `m2` might be ordered, but if a third -method cannot be sorted with them, they may cause an ambiguity together. - -For parametric types, the `ambiguous_bottom` keyword argument controls whether -`Union{}` counts as an ambiguous intersection of type parameters – when `true`, -it is considered ambiguous, when `false` it is not. - -# Examples -```jldoctest -julia> foo(x::Complex{<:Integer}) = 1 -foo (generic function with 1 method) - -julia> foo(x::Complex{<:Rational}) = 2 -foo (generic function with 2 methods) - -julia> m1, m2 = collect(methods(foo)); - -julia> typeintersect(m1.sig, m2.sig) -Tuple{typeof(foo), Complex{Union{}}} - -julia> Base.isambiguous(m1, m2, ambiguous_bottom=true) -true - -julia> Base.isambiguous(m1, m2, ambiguous_bottom=false) -false -``` -""" -function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) - m1 === m2 && return false - ti = typeintersect(m1.sig, m2.sig) - ti === Bottom && return false - function inner(ti) - ti === Bottom && return false - if !ambiguous_bottom - has_bottom_parameter(ti) && return false - end - world = get_world_counter() - world == typemax(UInt) && return true # intersecting methods are always ambiguous in the generator world, which is true, albeit maybe confusing for some - min = Ref{UInt}(typemin(UInt)) - max = Ref{UInt}(typemax(UInt)) - has_ambig = Ref{Int32}(0) - ms = collect(Core.MethodMatch, _methods_by_ftype(ti, nothing, -1, world, true, min, max, has_ambig)::Vector) - has_ambig[] == 0 && return false - if !ambiguous_bottom - filter!(ms) do m::Core.MethodMatch - return !has_bottom_parameter(m.spec_types) - end - end - # if ml-matches reported the existence of an ambiguity over their - # intersection, see if both m1 and m2 seem to be involved in it - # (if one was fully dominated by a different method, we want to will - # report the other ambiguous pair) - have_m1 = have_m2 = false - for match in ms - m = match.method - m === m1 && (have_m1 = true) - m === m2 && (have_m2 = true) - end - if !have_m1 || !have_m2 - # ml-matches did not need both methods to expose the reported ambiguity - return false - end - if !ambiguous_bottom - # since we're intentionally ignoring certain ambiguities (via the - # filter call above), see if we can now declare the intersection fully - # covered even though it is partially ambiguous over Union{} as a type - # parameter somewhere - minmax = nothing - for match in ms - m = match.method - match.fully_covers || continue - if minmax === nothing || morespecific(m, minmax) - minmax = m - end - end - if minmax === nothing || minmax == m1 || minmax == m2 - return true - end - for match in ms - m = match.method - m === minmax && continue - if !morespecific(minmax, m) - if match.fully_covers || !morespecific(m, minmax) - return true - end - end - end - return false - end - return true - end - if !(ti <: m1.sig && ti <: m2.sig) - # When type-intersection fails, it's often also not commutative. Thus - # checking the reverse may allow detecting ambiguity solutions - # correctly in more cases (and faster). - ti2 = typeintersect(m2.sig, m1.sig) - if ti2 <: m1.sig && ti2 <: m2.sig - ti = ti2 - elseif ti != ti2 - # TODO: this would be the more correct way to handle this case, but - # people complained so we don't do it - #inner(ti2) || return false # report that the type system failed to decide if it was ambiguous by saying they definitely are - return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not - else - return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not - end - end - inner(ti) || return false - # otherwise type-intersection reported an ambiguity we couldn't solve - return true -end - -""" - delete_method(m::Method) - -Make method `m` uncallable and force recompilation of any methods that use(d) it. -""" -function delete_method(m::Method) - ccall(:jl_method_table_disable, Cvoid, (Any, Any), get_methodtable(m), m) -end - -function get_methodtable(m::Method) - mt = ccall(:jl_method_get_table, Any, (Any,), m) - if mt === nothing - return nothing - end - return mt::Core.MethodTable -end - -""" - has_bottom_parameter(t) -> Bool - -Determine whether `t` is a Type for which one or more of its parameters is `Union{}`. -""" -function has_bottom_parameter(t::DataType) - for p in t.parameters - has_bottom_parameter(p) && return true - end - return false -end -has_bottom_parameter(t::typeof(Bottom)) = true -has_bottom_parameter(t::UnionAll) = has_bottom_parameter(unwrap_unionall(t)) -has_bottom_parameter(t::Union) = has_bottom_parameter(t.a) & has_bottom_parameter(t.b) -has_bottom_parameter(t::TypeVar) = has_bottom_parameter(t.ub) -has_bottom_parameter(::Any) = false - -min_world(m::Core.CodeInstance) = m.min_world -max_world(m::Core.CodeInstance) = m.max_world -min_world(m::Core.CodeInfo) = m.min_world -max_world(m::Core.CodeInfo) = m.max_world - -""" - get_world_counter() - -Returns the current maximum world-age counter. This counter is global and monotonically -increasing. -""" -get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) - -""" - tls_world_age() - -Returns the world the [current_task()](@ref) is executing within. -""" -tls_world_age() = ccall(:jl_get_tls_world_age, UInt, ()) - -""" - propertynames(x, private=false) - -Get a tuple or a vector of the properties (`x.property`) of an object `x`. -This is typically the same as [`fieldnames(typeof(x))`](@ref), but types -that overload [`getproperty`](@ref) should generally overload `propertynames` -as well to get the properties of an instance of the type. - -`propertynames(x)` may return only "public" property names that are part -of the documented interface of `x`. If you want it to also return "private" -property names intended for internal use, pass `true` for the optional second argument. -REPL tab completion on `x.` shows only the `private=false` properties. - -See also: [`hasproperty`](@ref), [`hasfield`](@ref). -""" -propertynames(x) = fieldnames(typeof(x)) -propertynames(m::Module) = names(m) -propertynames(x, private::Bool) = propertynames(x) # ignore private flag by default -propertynames(x::Array) = () # hide the fields from tab completion to discourage calling `x.size` instead of `size(x)`, even though they are equivalent - -""" - hasproperty(x, s::Symbol) - -Return a boolean indicating whether the object `x` has `s` as one of its own properties. - -!!! compat "Julia 1.2" - This function requires at least Julia 1.2. - -See also: [`propertynames`](@ref), [`hasfield`](@ref). -""" -hasproperty(x, s::Symbol) = s in propertynames(x) - -""" - @invoke f(arg::T, ...; kwargs...) - -Provides a convenient way to call [`invoke`](@ref) by expanding -`@invoke f(arg1::T1, arg2::T2; kwargs...)` to `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`. -When an argument's type annotation is omitted, it's replaced with `Core.Typeof` that argument. -To invoke a method where an argument is untyped or explicitly typed as `Any`, annotate the -argument with `::Any`. - -It also supports the following syntax: -- `@invoke (x::X).f` expands to `invoke(getproperty, Tuple{X,Symbol}, x, :f)` -- `@invoke (x::X).f = v::V` expands to `invoke(setproperty!, Tuple{X,Symbol,V}, x, :f, v)` -- `@invoke (xs::Xs)[i::I]` expands to `invoke(getindex, Tuple{Xs,I}, xs, i)` -- `@invoke (xs::Xs)[i::I] = v::V` expands to `invoke(setindex!, Tuple{Xs,V,I}, xs, v, i)` - -# Examples - -```jldoctest -julia> @macroexpand @invoke f(x::T, y) -:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y)) - -julia> @invoke 420::Integer % Unsigned -0x00000000000001a4 - -julia> @macroexpand @invoke (x::X).f -:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f)) - -julia> @macroexpand @invoke (x::X).f = v::V -:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v)) - -julia> @macroexpand @invoke (xs::Xs)[i::I] -:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i)) - -julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V -:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i)) -``` - -!!! compat "Julia 1.7" - This macro requires Julia 1.7 or later. - -!!! compat "Julia 1.9" - This macro is exported as of Julia 1.9. - -!!! compat "Julia 1.10" - The additional syntax is supported as of Julia 1.10. -""" -macro invoke(ex) - topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally - f, args, kwargs = destructure_callex(topmod, ex) - types = Expr(:curly, :Tuple) - out = Expr(:call, GlobalRef(Core, :invoke)) - isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) - push!(out.args, f) - push!(out.args, types) - for arg in args - if isexpr(arg, :(::)) - push!(out.args, arg.args[1]) - push!(types.args, arg.args[2]) - else - push!(out.args, arg) - push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg)) - end - end - return esc(out) -end - -""" - @invokelatest f(args...; kwargs...) - -Provides a convenient way to call [`invokelatest`](@ref). -`@invokelatest f(args...; kwargs...)` will simply be expanded into -`Base.invokelatest(f, args...; kwargs...)`. - -It also supports the following syntax: -- `@invokelatest x.f` expands to `Base.invokelatest(getproperty, x, :f)` -- `@invokelatest x.f = v` expands to `Base.invokelatest(setproperty!, x, :f, v)` -- `@invokelatest xs[i]` expands to `Base.invokelatest(getindex, xs, i)` -- `@invokelatest xs[i] = v` expands to `Base.invokelatest(setindex!, xs, v, i)` - -```jldoctest -julia> @macroexpand @invokelatest f(x; kw=kwv) -:(Base.invokelatest(f, x; kw = kwv)) - -julia> @macroexpand @invokelatest x.f -:(Base.invokelatest(Base.getproperty, x, :f)) - -julia> @macroexpand @invokelatest x.f = v -:(Base.invokelatest(Base.setproperty!, x, :f, v)) - -julia> @macroexpand @invokelatest xs[i] -:(Base.invokelatest(Base.getindex, xs, i)) - -julia> @macroexpand @invokelatest xs[i] = v -:(Base.invokelatest(Base.setindex!, xs, v, i)) -``` - -!!! compat "Julia 1.7" - This macro requires Julia 1.7 or later. - -!!! compat "Julia 1.9" - Prior to Julia 1.9, this macro was not exported, and was called as `Base.@invokelatest`. - -!!! compat "Julia 1.10" - The additional `x.f` and `xs[i]` syntax requires Julia 1.10. -""" -macro invokelatest(ex) - topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally - f, args, kwargs = destructure_callex(topmod, ex) - out = Expr(:call, GlobalRef(Base, :invokelatest)) - isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) - push!(out.args, f) - append!(out.args, args) - return esc(out) -end - -function destructure_callex(topmod::Module, @nospecialize(ex)) - function flatten(xs) - out = Any[] - for x in xs - if isexpr(x, :tuple) - append!(out, x.args) - else - push!(out, x) - end - end - return out - end - - kwargs = Any[] - if isexpr(ex, :call) # `f(args...)` - f = first(ex.args) - args = Any[] - for x in ex.args[2:end] - if isexpr(x, :parameters) - append!(kwargs, x.args) - elseif isexpr(x, :kw) - push!(kwargs, x) - else - push!(args, x) - end - end - elseif isexpr(ex, :.) # `x.f` - f = GlobalRef(topmod, :getproperty) - args = flatten(ex.args) - elseif isexpr(ex, :ref) # `x[i]` - f = GlobalRef(topmod, :getindex) - args = flatten(ex.args) - elseif isexpr(ex, :(=)) # `x.f = v` or `x[i] = v` - lhs, rhs = ex.args - if isexpr(lhs, :.) - f = GlobalRef(topmod, :setproperty!) - args = flatten(Any[lhs.args..., rhs]) - elseif isexpr(lhs, :ref) - f = GlobalRef(topmod, :setindex!) - args = flatten(Any[lhs.args[1], rhs, lhs.args[2]]) - else - throw(ArgumentError("expected a `setproperty!` expression `x.f = v` or `setindex!` expression `x[i] = v`")) - end - else - throw(ArgumentError("expected a `:call` expression `f(args...; kwargs...)`")) - end - return f, args, kwargs -end - -""" - Base.generating_output([incremental::Bool])::Bool - -Return `true` if the current process is being used to pre-generate a -code cache via any of the `--output-*` command line arguments. The optional -`incremental` argument further specifies the precompilation mode: when set -to `true`, the function will return `true` only for package precompilation; -when set to `false`, it will return `true` only for system image generation. - -!!! compat "Julia 1.11" - This function requires at least Julia 1.11. -""" -function generating_output(incremental::Union{Bool,Nothing}=nothing) - ccall(:jl_generating_output, Cint, ()) == 0 && return false - if incremental !== nothing - JLOptions().incremental == incremental || return false - end - return true -end +hasintersect(@nospecialize(a), @nospecialize(b)) = typeintersect(a, b) !== Bottom diff --git a/base/reflection2.jl b/base/reflection2.jl new file mode 100644 index 00000000000000..8fe8d324eb7923 --- /dev/null +++ b/base/reflection2.jl @@ -0,0 +1,1466 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +const Compiler = Core.Compiler + +""" + code_lowered(f, types; generated=true, debuginfo=:default) + +Return an array of the lowered forms (IR) for the methods matching the given generic function +and type signature. + +If `generated` is `false`, the returned `CodeInfo` instances will correspond to fallback +implementations. An error is thrown if no fallback implementation exists. +If `generated` is `true`, these `CodeInfo` instances will correspond to the method bodies +yielded by expanding the generators. + +The keyword `debuginfo` controls the amount of code metadata present in the output. + +Note that an error will be thrown if `types` are not leaf types when `generated` is +`true` and any of the corresponding methods are an `@generated` method. +""" +function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool=true, debuginfo::Symbol=:default) + if @isdefined(IRShow) + debuginfo = IRShow.debuginfo(debuginfo) + elseif debuginfo === :default + debuginfo = :source + end + if debuginfo !== :source && debuginfo !== :none + throw(ArgumentError("'debuginfo' must be either :source or :none")) + end + world = get_world_counter() + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + ret = CodeInfo[] + for m in method_instances(f, t, world) + if generated && hasgenerator(m) + if may_invoke_generator(m) + code = ccall(:jl_code_for_staged, Ref{CodeInfo}, (Any, UInt, Ptr{Cvoid}), m, world, C_NULL) + else + error("Could not expand generator for `@generated` method ", m, ". ", + "This can happen if the provided argument types (", t, ") are ", + "not leaf types, but the `generated` argument is `true`.") + end + else + code = uncompressed_ir(m.def::Method) + debuginfo === :none && remove_linenums!(code) + end + push!(ret, code) + end + return ret +end + +# high-level, more convenient method lookup functions + +function visit(f, mt::Core.MethodTable) + mt.defs !== nothing && visit(f, mt.defs) + nothing +end +function visit(f, mc::Core.TypeMapLevel) + function avisit(f, e::Memory{Any}) + for i in 2:2:length(e) + isassigned(e, i) || continue + ei = e[i] + if ei isa Memory{Any} + for j in 2:2:length(ei) + isassigned(ei, j) || continue + visit(f, ei[j]) + end + else + visit(f, ei) + end + end + end + if mc.targ !== nothing + avisit(f, mc.targ::Memory{Any}) + end + if mc.arg1 !== nothing + avisit(f, mc.arg1::Memory{Any}) + end + if mc.tname !== nothing + avisit(f, mc.tname::Memory{Any}) + end + if mc.name1 !== nothing + avisit(f, mc.name1::Memory{Any}) + end + mc.list !== nothing && visit(f, mc.list) + mc.any !== nothing && visit(f, mc.any) + nothing +end +function visit(f, d::Core.TypeMapEntry) + while d !== nothing + f(d.func) + d = d.next + end + nothing +end +struct MethodSpecializations + specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} +end +""" + specializations(m::Method) → itr + +Return an iterator `itr` of all compiler-generated specializations of `m`. +""" +specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) +function iterate(specs::MethodSpecializations) + s = specs.specializations + s === nothing && return nothing + isa(s, Core.MethodInstance) && return (s, nothing) + return iterate(specs, 0) +end +iterate(specs::MethodSpecializations, ::Nothing) = nothing +function iterate(specs::MethodSpecializations, i::Int) + s = specs.specializations::Core.SimpleVector + n = length(s) + i >= n && return nothing + item = nothing + while i < n && item === nothing + item = s[i+=1] + end + item === nothing && return nothing + return (item, i) +end +length(specs::MethodSpecializations) = count(Returns(true), specs) + +function length(mt::Core.MethodTable) + n = 0 + visit(mt) do m + n += 1 + end + return n::Int +end +isempty(mt::Core.MethodTable) = (mt.defs === nothing) + +uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) : + isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : + error("Code for this Method is not available.") +function _uncompressed_ir(m::Method) + s = m.source + if s isa String + s = ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Ptr{Cvoid}, Any), m, C_NULL, s) + end + return s::CodeInfo +end + +# for backwards compat +const uncompressed_ast = uncompressed_ir +const _uncompressed_ast = _uncompressed_ir + +function method_instances(@nospecialize(f), @nospecialize(t), world::UInt) + tt = signature_type(f, t) + results = Core.MethodInstance[] + # this make a better error message than the typeassert that follows + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + for match in _methods_by_ftype(tt, -1, world)::Vector + instance = Core.Compiler.specialize_method(match::Core.MethodMatch) + push!(results, instance) + end + return results +end + +function method_instance(@nospecialize(f), @nospecialize(t); + world=Base.get_world_counter(), method_table=nothing) + tt = signature_type(f, t) + mi = ccall(:jl_method_lookup_by_tt, Any, + (Any, Csize_t, Any), + tt, world, method_table) + return mi::Union{Nothing, MethodInstance} +end + +default_debug_info_kind() = unsafe_load(cglobal(:jl_default_debug_info_kind, Cint)) + +# this type mirrors jl_cgparams_t (documented in julia.h) +struct CodegenParams + """ + If enabled, generate the necessary code to support the --track-allocations + command line flag to julia itself. Note that the option itself does not enable + allocation tracking. Rather, it merely generates the support code necessary + to perform allocation tracking if requested by the command line option. + """ + track_allocations::Cint + + """ + If enabled, generate the necessary code to support the --code-coverage + command line flag to julia itself. Note that the option itself does not enable + code coverage. Rather, it merely generates the support code necessary + to code coverage if requested by the command line option. + """ + code_coverage::Cint + + """ + If enabled, force the compiler to use the specialized signature + for all generated functions, whenever legal. If disabled, the choice is made + heuristically and specsig is only used when deemed profitable. + """ + prefer_specsig::Cint + + """ + If enabled, enable emission of `.debug_names` sections. + """ + gnu_pubnames::Cint + + """ + Controls what level of debug info to emit. Currently supported values are: + - 0: no debug info + - 1: full debug info + - 2: Line tables only + - 3: Debug directives only + + The integer values currently match the llvm::DICompilerUnit::DebugEmissionKind enum, + although this is not guaranteed. + """ + debug_info_kind::Cint + + """ + Controls the debug_info_level parameter, equivalent to the -g command line option. + """ + debug_info_level::Cint + + """ + If enabled, generate a GC safepoint at the entry to every function. Emitting + these extra safepoints can reduce the amount of time that other threads are + waiting for the currently running thread to reach a safepoint. The cost for + a safepoint is small, but non-zero. The option is enabled by default. + """ + safepoint_on_entry::Cint + + """ + If enabled, add an implicit argument to each function call that is used to + pass down the current task local state pointer. This argument is passed + using the `swiftself` convention, which in the ordinary case means that the + pointer is kept in a register and accesses are thus very fast. If this option + is disabled, the task local state pointer must be loaded from thread local + storage, which incurs a small amount of additional overhead. The option is enabled by + default. + """ + gcstack_arg::Cint + + """ + If enabled, use the Julia PLT mechanism to support lazy-resolution of `ccall` + targets. The option may be disabled for use in environments where the julia + runtime is unavailable, but is otherwise recommended to be enabled, even if + lazy resolution is not required, as the Julia PLT mechanism may have superior + performance compared to the native platform mechanism. The options is enabled by default. + """ + use_jlplt::Cint + + """ + If enabled, only provably reachable code (from functions marked with `entrypoint`) is included + in the output system image. Errors or warnings can be given for call sites too dynamic to handle. + The option is disabled by default. (0=>disabled, 1=>safe (static errors), 2=>unsafe, 3=>unsafe plus warnings) + """ + trim::Cint + + """ + A pointer of type + + typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, + size_t min_world, size_t max_world); + + that may be used by external compilers as a callback to look up the code instance corresponding + to a particular method instance. + """ + lookup::Ptr{Cvoid} + + function CodegenParams(; track_allocations::Bool=true, code_coverage::Bool=true, + prefer_specsig::Bool=false, + gnu_pubnames::Bool=true, debug_info_kind::Cint = default_debug_info_kind(), + debug_info_level::Cint = Cint(JLOptions().debug_level), safepoint_on_entry::Bool=true, + gcstack_arg::Bool=true, use_jlplt::Bool=true, trim::Cint=Cint(0), + lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid}))) + return new( + Cint(track_allocations), Cint(code_coverage), + Cint(prefer_specsig), + Cint(gnu_pubnames), debug_info_kind, + debug_info_level, Cint(safepoint_on_entry), + Cint(gcstack_arg), Cint(use_jlplt), Cint(trim), + lookup) + end +end + +# this type mirrors jl_emission_params_t (documented in julia.h) +struct EmissionParams + emit_metadata::Cint + + function EmissionParams(; emit_metadata::Bool=true) + return new(Cint(emit_metadata)) + end +end + +""" + code_typed(f, types; kw...) + +Returns an array of type-inferred lowered form (IR) for the methods matching the given +generic function and type signature. + +# Keyword Arguments + +- `optimize::Bool = true`: optional, controls whether additional optimizations, + such as inlining, are also applied. +- `debuginfo::Symbol = :default`: optional, controls the amount of code metadata present + in the output, possible options are `:source` or `:none`. + +# Internal Keyword Arguments + +This section should be considered internal, and is only for who understands Julia compiler +internals. + +- `world::UInt = Base.get_world_counter()`: optional, controls the world age to use + when looking up methods, use current world age if not specified. +- `interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world)`: + optional, controls the abstract interpreter to use, use the native interpreter if not specified. + +# Examples + +One can put the argument types in a tuple to get the corresponding `code_typed`. + +```julia +julia> code_typed(+, (Float64, Float64)) +1-element Vector{Any}: + CodeInfo( +1 ─ %1 = Base.add_float(x, y)::Float64 +└── return %1 +) => Float64 +``` +""" +function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); kwargs...) + if isa(f, Core.OpaqueClosure) + return code_typed_opaque_closure(f, types; kwargs...) + end + tt = signature_type(f, types) + return code_typed_by_type(tt; kwargs...) +end + +# returns argument tuple type which is supposed to be used for `code_typed` and its family; +# if there is a single method this functions returns the method argument signature, +# otherwise returns `Tuple` that doesn't match with any signature +function default_tt(@nospecialize(f)) + ms = methods(f) + if length(ms) == 1 + return tuple_type_tail(only(ms).sig) + else + return Tuple + end +end + +function raise_match_failure(name::Symbol, @nospecialize(tt)) + @noinline + sig_str = sprint(Base.show_tuple_as_call, Symbol(""), tt) + error("$name: unanalyzable call given $sig_str") +end + +""" + code_typed_by_type(types::Type{<:Tuple}; ...) + +Similar to [`code_typed`](@ref), except the argument is a tuple type describing +a full signature to query. +""" +function code_typed_by_type(@nospecialize(tt::Type); + optimize::Bool=true, + debuginfo::Symbol=:default, + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + if @isdefined(IRShow) + debuginfo = IRShow.debuginfo(debuginfo) + elseif debuginfo === :default + debuginfo = :source + end + if debuginfo !== :source && debuginfo !== :none + throw(ArgumentError("'debuginfo' must be either :source or :none")) + end + tt = to_tuple_type(tt) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:code_typed, tt) + asts = [] + for match in matches.matches + match = match::Core.MethodMatch + code = Compiler.typeinf_code(interp, match, optimize) + if code === nothing + push!(asts, match.method => Any) + else + debuginfo === :none && remove_linenums!(code) + push!(asts, code => code.rettype) + end + end + return asts +end + +function get_oc_code_rt(oc::Core.OpaqueClosure, types, optimize::Bool) + @nospecialize oc types + ccall(:jl_is_in_pure_context, Bool, ()) && + error("code reflection cannot be used from generated functions") + m = oc.source + if isa(m, Method) + if isdefined(m, :source) + if optimize + tt = Tuple{typeof(oc.captures), to_tuple_type(types).parameters...} + mi = Compiler.specialize_method(m, tt, Core.svec()) + interp = Compiler.NativeInterpreter(m.primary_world) + code = Compiler.typeinf_code(interp, mi, optimize) + if code isa CodeInfo + return Pair{CodeInfo, Any}(code, code.rettype) + end + error("inference not successful") + else + code = _uncompressed_ir(m) + return Pair{CodeInfo, Any}(code, typeof(oc).parameters[2]) + end + else + # OC constructed from optimized IR + codeinst = m.specializations.cache + # XXX: the inferred field is not normally a CodeInfo, but this assumes it is guaranteed to be always + return Pair{CodeInfo, Any}(codeinst.inferred, codeinst.rettype) + end + else + error("encountered invalid Core.OpaqueClosure object") + end +end + +function code_typed_opaque_closure(oc::Core.OpaqueClosure, types; + debuginfo::Symbol=:default, + optimize::Bool=true, + _...) + @nospecialize oc types + (code, rt) = get_oc_code_rt(oc, types, optimize) + debuginfo === :none && remove_linenums!(code) + return Any[Pair{CodeInfo,Any}(code, rt)] +end + +""" + code_ircode(f, [types]) + +Return an array of pairs of `IRCode` and inferred return type if type inference succeeds. +The `Method` is included instead of `IRCode` otherwise. + +See also: [`code_typed`](@ref) + +# Internal Keyword Arguments + +This section should be considered internal, and is only for who understands Julia compiler +internals. + +- `world::UInt = Base.get_world_counter()`: optional, controls the world age to use + when looking up methods, use current world age if not specified. +- `interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world)`: + optional, controls the abstract interpreter to use, use the native interpreter if not specified. +- `optimize_until::Union{Integer,AbstractString,Nothing} = nothing`: optional, + controls the optimization passes to run. + If it is a string, it specifies the name of the pass up to which the optimizer is run. + If it is an integer, it specifies the number of passes to run. + If it is `nothing` (default), all passes are run. + +# Examples + +One can put the argument types in a tuple to get the corresponding `code_ircode`. + +```julia +julia> Base.code_ircode(+, (Float64, Int64)) +1-element Vector{Any}: + 388 1 ─ %1 = Base.sitofp(Float64, _3)::Float64 + │ %2 = Base.add_float(_2, %1)::Float64 + └── return %2 + => Float64 + +julia> Base.code_ircode(+, (Float64, Int64); optimize_until = "compact 1") +1-element Vector{Any}: + 388 1 ─ %1 = Base.promote(_2, _3)::Tuple{Float64, Float64} + │ %2 = Core._apply_iterate(Base.iterate, Base.:+, %1)::Float64 + └── return %2 + => Float64 +``` +""" +function code_ircode(@nospecialize(f), @nospecialize(types = default_tt(f)); kwargs...) + if isa(f, Core.OpaqueClosure) + error("OpaqueClosure not supported") + end + tt = signature_type(f, types) + return code_ircode_by_type(tt; kwargs...) +end + +""" + code_ircode_by_type(types::Type{<:Tuple}; ...) + +Similar to [`code_ircode`](@ref), except the argument is a tuple type describing +a full signature to query. +""" +function code_ircode_by_type( + @nospecialize(tt::Type); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world), + optimize_until::Union{Integer,AbstractString,Nothing}=nothing, +) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + tt = to_tuple_type(tt) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:code_ircode, tt) + asts = [] + for match in matches.matches + match = match::Core.MethodMatch + (code, ty) = Compiler.typeinf_ircode(interp, match, optimize_until) + if code === nothing + push!(asts, match.method => Any) + else + push!(asts, code => ty) + end + end + return asts +end + +function _builtin_return_type(interp::Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Compiler.widenconst(rt) +end + +function _builtin_effects(interp::Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Compiler.builtin_effects(Compiler.typeinf_lattice(interp), f, argtypes, rt) +end + +function _builtin_exception_type(interp::Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + effects = _builtin_effects(interp, f, types) + return Compiler.is_nothrow(effects) ? Union{} : Any +end + +check_generated_context(world::UInt) = + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + +# TODO rename `Base.return_types` to `Base.infer_return_types` + +""" + Base.return_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} + +Return a list of possible return types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rts::Vector{Any}`: The list of return types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + +# Examples + +```julia +julia> Base.return_types(sum, Tuple{Vector{Int}}) +1-element Vector{Any}: + Int64 + +julia> methods(sum, (Union{Vector{Int},UnitRange{Int}},)) +# 2 methods for generic function "sum" from Base: + [1] sum(r::AbstractRange{<:Real}) + @ range.jl:1399 + [2] sum(a::AbstractArray; dims, kw...) + @ reducedim.jl:1010 + +julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) +2-element Vector{Any}: + Int64 # the result of inference on sum(r::AbstractRange{<:Real}) + Int64 # the result of inference on sum(a::AbstractArray; dims, kw...) +``` + +!!! warning + The `Base.return_types` function should not be used from generated functions; + doing so will result in an error. +""" +function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + _, rt = only(code_typed_opaque_closure(f, types)) + return Any[rt] + elseif isa(f, Core.Builtin) + return Any[_builtin_return_type(interp, f, types)] + end + tt = signature_type(f, types) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:return_types, tt) + rts = Any[] + for match in matches.matches + ty = Compiler.typeinf_type(interp, match::Core.MethodMatch) + push!(rts, something(ty, Any)) + end + return rts +end + +""" + Base.infer_return_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> rt::Type + +Returns an inferred return type of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rt::Type`: An inferred return type of the function call specified by the given call signature. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + return types of every possible method matching with the given `f` and `types`. + It returns a single return type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Examples + +```julia +julia> checksym(::Symbol) = :symbol; + +julia> checksym(x::Any) = x; + +julia> Base.infer_return_type(checksym, (Union{Symbol,String},)) +Union{String, Symbol} + +julia> Base.return_types(checksym, (Union{Symbol,String},)) +2-element Vector{Any}: + Symbol + Union{String, Symbol} +``` + +It's important to note the difference here: `Base.return_types` gives back inferred results +for each method that matches the given signature `checksum(::Union{Symbol,String})`. +On the other hand `Base.infer_return_type` returns one collective result that sums up all those possibilities. + +!!! warning + The `Base.infer_return_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_return_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return last(only(code_typed_opaque_closure(f, types))) + elseif isa(f, Core.Builtin) + return _builtin_return_type(interp, f, types) + end + tt = signature_type(f, types) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:infer_return_type, tt) + rt = Union{} + for match in matches.matches + ty = Compiler.typeinf_type(interp, match::Core.MethodMatch) + rt = Compiler.tmerge(rt, something(ty, Any)) + end + return rt +end + +""" + Base.infer_exception_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} + +Return a list of possible exception types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `excts::Vector{Any}`: The list of exception types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + +# Examples + +```julia +julia> throw_if_number(::Number) = error("number is given"); + +julia> throw_if_number(::Any) = nothing; + +julia> Base.infer_exception_types(throw_if_number, (Int,)) +1-element Vector{Any}: + ErrorException + +julia> methods(throw_if_number, (Any,)) +# 2 methods for generic function "throw_if_number" from Main: + [1] throw_if_number(x::Number) + @ REPL[1]:1 + [2] throw_if_number(::Any) + @ REPL[2]:1 + +julia> Base.infer_exception_types(throw_if_number, (Any,)) +2-element Vector{Any}: + ErrorException # the result of inference on `throw_if_number(::Number)` + Union{} # the result of inference on `throw_if_number(::Any)` +``` + +!!! warning + The `Base.infer_exception_types` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any[Any] # TODO + elseif isa(f, Core.Builtin) + return Any[_builtin_exception_type(interp, f, types)] + end + tt = signature_type(f, types) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:infer_exception_types, tt) + excts = Any[] + for match in matches.matches + frame = Compiler.typeinf_frame(interp, match::Core.MethodMatch, #=run_optimizer=#false) + if frame === nothing + exct = Any + else + exct = Compiler.widenconst(frame.result.exc_result) + end + push!(excts, exct) + end + return excts +end + +_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = + matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + +""" + Base.infer_exception_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type + +Returns the type of exception potentially thrown by the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `exct::Type`: The inferred type of exception that can be thrown by the function call + specified by the given call signature. + +!!! note + Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list + exception types for every possible matching method with the given `f` and `types`. + It returns a single exception type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Examples + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_exception_type(f1, (Int,)) +Union{} +``` + +The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_exception_type(f2, (Integer,)) +MethodError +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the exception type is widened to `MethodError`. + +!!! warning + The `Base.infer_exception_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any # TODO + elseif isa(f, Core.Builtin) + return _builtin_exception_type(interp, f, types) + end + tt = signature_type(f, types) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:infer_exception_type, tt) + exct = Union{} + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + exct = Compiler.tmerge(exct, MethodError) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + frame === nothing && return Any + exct = Compiler.tmerge(exct, Compiler.widenconst(frame.result.exc_result)) + end + return exct +end + +""" + Base.infer_effects( + f, types=default_tt(f); + optimize::Bool=true, + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects + +Returns the possible computation effects of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `optimize` (optional): Whether to run additional effects refinements based on post-optimization analysis. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `effects::Effects`: The computed effects of the function call specified by the given call signature. + See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) + for more information on the various effect properties. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + effect analysis results for every possible matching method with the given `f` and `types`. + It returns a single effect, taking into account all potential outcomes of any function + call entailed by the given signature type. + +# Examples + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_effects(f1, (Int,)) +(+c,+e,+n,+t,+s,+m,+i) +``` + +This function will return an `Effects` object with information about the computational +effects of the function `f1` when called with an `Int` argument. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_effects(f2, (Integer,)) +(+c,+e,!n,+t,+s,+m,+i) +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the `:nothrow` bit gets tainted. + +!!! warning + The `Base.infer_effects` function should not be used from generated functions; + doing so will result in an error. + +# See Also +- [`Core.Compiler.Effects`](@ref): A type representing the computational effects of a method call. +- [`Base.@assume_effects`](@ref): A macro for making assumptions about the effects of a method. +""" +function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); + optimize::Bool=true, + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.Builtin) + return _builtin_effects(interp, f, types) + end + tt = signature_type(f, types) + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:infer_effects, tt) + effects = Compiler.EFFECTS_TOTAL + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + effects = Compiler.Effects(effects; nothrow=false) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Compiler.typeinf_frame(interp, match, #=run_optimizer=#optimize) + frame === nothing && return Compiler.Effects() + effects = Compiler.merge_effects(effects, frame.result.ipo_effects) + end + return effects +end + +""" + print_statement_costs(io::IO, f, types) + +Print type-inferred and optimized code for `f` given argument types `types`, +prepending each line with its cost as estimated by the compiler's inlining engine. +""" +function print_statement_costs(io::IO, @nospecialize(f), @nospecialize(t); kwargs...) + tt = signature_type(f, t) + print_statement_costs(io, tt; kwargs...) +end + +function print_statement_costs(io::IO, @nospecialize(tt::Type); + world::UInt=get_world_counter(), + interp::Compiler.AbstractInterpreter=Compiler.NativeInterpreter(world)) + tt = to_tuple_type(tt) + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + matches = Compiler.findall(tt, Compiler.method_table(interp)) + matches === nothing && raise_match_failure(:print_statement_costs, tt) + params = Compiler.OptimizationParams(interp) + cst = Int[] + for match in matches.matches + match = match::Core.MethodMatch + println(io, match.method) + code = Compiler.typeinf_code(interp, match, true) + if code === nothing + println(io, " inference not successful") + else + empty!(cst) + resize!(cst, length(code.code)) + sptypes = Compiler.VarState[Compiler.VarState(sp, false) for sp in match.sparams] + maxcost = Compiler.statement_costs!(cst, code.code, code, sptypes, params) + nd = ndigits(maxcost) + irshow_config = IRShow.IRShowConfig() do io, linestart, idx + print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ") + return "" + end + IRShow.show_ir(io, code, irshow_config) + end + println(io) + end +end + +print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args...; kwargs...) + +function _which(@nospecialize(tt::Type); + method_table::Union{Nothing,Core.MethodTable,Compiler.MethodTableView}=nothing, + world::UInt=get_world_counter(), + raise::Bool=true) + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + if method_table === nothing + table = Compiler.InternalMethodTable(world) + elseif method_table isa Core.MethodTable + table = Compiler.OverlayMethodTable(world, method_table) + else + table = method_table + end + match, = Compiler.findsup(tt, table) + if match === nothing + raise && error("no unique matching method found for the specified argument types") + return nothing + end + return match +end + +""" + which(f, types) + +Returns the method of `f` (a `Method` object) that would be called for arguments of the given `types`. + +If `types` is an abstract type, then the method that would be called by `invoke` is returned. + +See also: [`parentmodule`](@ref), [`@which`](@ref Main.InteractiveUtils.@which), and [`@edit`](@ref Main.InteractiveUtils.@edit). +""" +function which(@nospecialize(f), @nospecialize(t)) + tt = signature_type(f, t) + world = get_world_counter() + match, _ = Compiler._findsup(tt, nothing, world) + if match === nothing + me = MethodError(f, t, world) + ee = ErrorException(sprint(io -> begin + println(io, "Calling invoke(f, t, args...) would throw:"); + Base.showerror(io, me); + end)) + throw(ee) + end + return match.method +end + +""" + which(types::Type{<:Tuple}) + +Returns the method that would be called by the given type signature (as a tuple type). +""" +function which(@nospecialize(tt#=::Type=#)) + return _which(tt).method +end + +""" + which(module, symbol) + +Return the module in which the binding for the variable referenced by `symbol` in `module` was created. +""" +function which(m::Module, s::Symbol) + if !isdefined(m, s) + error("\"$s\" is not defined in module $m") + end + return binding_module(m, s) +end + +# function reflection + +""" + nameof(f::Function) -> Symbol + +Get the name of a generic `Function` as a symbol. For anonymous functions, +this is a compiler-generated name. For explicitly-declared subtypes of +`Function`, it is the name of the function's type. +""" +function nameof(f::Function) + t = typeof(f) + mt = t.name.mt + if mt === Symbol.name.mt + # uses shared method table, so name is not unique to this function type + return nameof(t) + end + return mt.name +end + +function nameof(f::Core.IntrinsicFunction) + name = ccall(:jl_intrinsic_name, Ptr{UInt8}, (Core.IntrinsicFunction,), f) + return ccall(:jl_symbol, Ref{Symbol}, (Ptr{UInt8},), name) +end + +""" + parentmodule(f::Function) -> Module + +Determine the module containing the (first) definition of a generic +function. +""" +parentmodule(f::Function) = parentmodule(typeof(f)) + +""" + parentmodule(f::Function, types) -> Module + +Determine the module containing the first method of a generic function `f` matching +the specified `types`. +""" +function parentmodule(@nospecialize(f), @nospecialize(types)) + m = methods(f, types) + if isempty(m) + error("no matching methods") + end + return parentmodule(first(m)) +end + +""" + parentmodule(m::Method) -> Module + +Return the module in which the given method `m` is defined. + +!!! compat "Julia 1.9" + Passing a `Method` as an argument requires Julia 1.9 or later. +""" +parentmodule(m::Method) = m.module + +""" + hasmethod(f, t::Type{<:Tuple}[, kwnames]; world=get_world_counter()) -> Bool + +Determine whether the given generic function has a method matching the given +`Tuple` of argument types with the upper bound of world age given by `world`. + +If a tuple of keyword argument names `kwnames` is provided, this also checks +whether the method of `f` matching `t` has the given keyword argument names. +If the matching method accepts a variable number of keyword arguments, e.g. +with `kwargs...`, any names given in `kwnames` are considered valid. Otherwise +the provided names must be a subset of the method's keyword arguments. + +See also [`applicable`](@ref). + +!!! compat "Julia 1.2" + Providing keyword argument names requires Julia 1.2 or later. + +# Examples +```jldoctest +julia> hasmethod(length, Tuple{Array}) +true + +julia> f(; oranges=0) = oranges; + +julia> hasmethod(f, Tuple{}, (:oranges,)) +true + +julia> hasmethod(f, Tuple{}, (:apples, :bananas)) +false + +julia> g(; xs...) = 4; + +julia> hasmethod(g, Tuple{}, (:a, :b, :c, :d)) # g accepts arbitrary kwargs +true +``` +""" +function hasmethod(@nospecialize(f), @nospecialize(t)) + return Core._hasmethod(signature_type(f, t)) +end + +function Core.kwcall(kwargs::NamedTuple, ::typeof(hasmethod), @nospecialize(f), @nospecialize(t)) + world = kwargs.world::UInt # make sure this is the only local, to avoid confusing kwarg_decl() + return ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), signature_type(f, t), nothing, world) !== nothing +end + +function hasmethod(f, t, kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_counter()) + @nospecialize + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + isempty(kwnames) && return hasmethod(f, t; world) + t = to_tuple_type(t) + ft = Core.Typeof(f) + u = unwrap_unionall(t)::DataType + tt = rewrap_unionall(Tuple{typeof(Core.kwcall), NamedTuple, ft, u.parameters...}, t) + match = ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tt, nothing, world) + match === nothing && return false + kws = ccall(:jl_uncompress_argnames, Array{Symbol,1}, (Any,), (match::Method).slot_syms) + isempty(kws) && return true # some kwfuncs simply forward everything directly + for kw in kws + endswith(String(kw), "...") && return true + end + kwnames = collect(kwnames) + return issubset(kwnames, kws) +end + +""" + fbody = bodyfunction(basemethod::Method) + +Find the keyword "body function" (the function that contains the body of the method +as written, called after all missing keyword-arguments have been assigned default values). +`basemethod` is the method you obtain via [`which`](@ref) or [`methods`](@ref). +""" +function bodyfunction(basemethod::Method) + fmod = parentmodule(basemethod) + # The lowered code for `basemethod` should look like + # %1 = mkw(kwvalues..., #self#, args...) + # return %1 + # where `mkw` is the name of the "active" keyword body-function. + ast = uncompressed_ast(basemethod) + if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 + callexpr = ast.code[end-1] + if isa(callexpr, Expr) && callexpr.head === :call + fsym = callexpr.args[1] + while true + if isa(fsym, Symbol) + return getfield(fmod, fsym) + elseif isa(fsym, GlobalRef) + if fsym.mod === Core && fsym.name === :_apply + fsym = callexpr.args[2] + elseif fsym.mod === Core && fsym.name === :_apply_iterate + fsym = callexpr.args[3] + end + if isa(fsym, Symbol) + return getfield(fmod, fsym)::Function + elseif isa(fsym, GlobalRef) + return getfield(fsym.mod, fsym.name)::Function + elseif isa(fsym, Core.SSAValue) + fsym = ast.code[fsym.id] + else + return nothing + end + elseif isa(fsym, Core.SSAValue) + fsym = ast.code[fsym.id] + else + return nothing + end + end + end + end + return nothing +end + +""" + Base.isambiguous(m1, m2; ambiguous_bottom=false) -> Bool + +Determine whether two methods `m1` and `m2` may be ambiguous for some call +signature. This test is performed in the context of other methods of the same +function; in isolation, `m1` and `m2` might be ambiguous, but if a third method +resolving the ambiguity has been defined, this returns `false`. +Alternatively, in isolation `m1` and `m2` might be ordered, but if a third +method cannot be sorted with them, they may cause an ambiguity together. + +For parametric types, the `ambiguous_bottom` keyword argument controls whether +`Union{}` counts as an ambiguous intersection of type parameters – when `true`, +it is considered ambiguous, when `false` it is not. + +# Examples +```jldoctest +julia> foo(x::Complex{<:Integer}) = 1 +foo (generic function with 1 method) + +julia> foo(x::Complex{<:Rational}) = 2 +foo (generic function with 2 methods) + +julia> m1, m2 = collect(methods(foo)); + +julia> typeintersect(m1.sig, m2.sig) +Tuple{typeof(foo), Complex{Union{}}} + +julia> Base.isambiguous(m1, m2, ambiguous_bottom=true) +true + +julia> Base.isambiguous(m1, m2, ambiguous_bottom=false) +false +``` +""" +function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) + m1 === m2 && return false + ti = typeintersect(m1.sig, m2.sig) + ti === Bottom && return false + function inner(ti) + ti === Bottom && return false + if !ambiguous_bottom + has_bottom_parameter(ti) && return false + end + world = get_world_counter() + world == typemax(UInt) && return true # intersecting methods are always ambiguous in the generator world, which is true, albeit maybe confusing for some + min = Ref{UInt}(typemin(UInt)) + max = Ref{UInt}(typemax(UInt)) + has_ambig = Ref{Int32}(0) + ms = collect(Core.MethodMatch, _methods_by_ftype(ti, nothing, -1, world, true, min, max, has_ambig)::Vector) + has_ambig[] == 0 && return false + if !ambiguous_bottom + filter!(ms) do m::Core.MethodMatch + return !has_bottom_parameter(m.spec_types) + end + end + # if ml-matches reported the existence of an ambiguity over their + # intersection, see if both m1 and m2 seem to be involved in it + # (if one was fully dominated by a different method, we want to will + # report the other ambiguous pair) + have_m1 = have_m2 = false + for match in ms + m = match.method + m === m1 && (have_m1 = true) + m === m2 && (have_m2 = true) + end + if !have_m1 || !have_m2 + # ml-matches did not need both methods to expose the reported ambiguity + return false + end + if !ambiguous_bottom + # since we're intentionally ignoring certain ambiguities (via the + # filter call above), see if we can now declare the intersection fully + # covered even though it is partially ambiguous over Union{} as a type + # parameter somewhere + minmax = nothing + for match in ms + m = match.method + match.fully_covers || continue + if minmax === nothing || morespecific(m, minmax) + minmax = m + end + end + if minmax === nothing || minmax == m1 || minmax == m2 + return true + end + for match in ms + m = match.method + m === minmax && continue + if !morespecific(minmax, m) + if match.fully_covers || !morespecific(m, minmax) + return true + end + end + end + return false + end + return true + end + if !(ti <: m1.sig && ti <: m2.sig) + # When type-intersection fails, it's often also not commutative. Thus + # checking the reverse may allow detecting ambiguity solutions + # correctly in more cases (and faster). + ti2 = typeintersect(m2.sig, m1.sig) + if ti2 <: m1.sig && ti2 <: m2.sig + ti = ti2 + elseif ti != ti2 + # TODO: this would be the more correct way to handle this case, but + # people complained so we don't do it + #inner(ti2) || return false # report that the type system failed to decide if it was ambiguous by saying they definitely are + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not + else + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not + end + end + inner(ti) || return false + # otherwise type-intersection reported an ambiguity we couldn't solve + return true +end + +""" + @invoke f(arg::T, ...; kwargs...) + +Provides a convenient way to call [`invoke`](@ref) by expanding +`@invoke f(arg1::T1, arg2::T2; kwargs...)` to `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`. +When an argument's type annotation is omitted, it's replaced with `Core.Typeof` that argument. +To invoke a method where an argument is untyped or explicitly typed as `Any`, annotate the +argument with `::Any`. + +It also supports the following syntax: +- `@invoke (x::X).f` expands to `invoke(getproperty, Tuple{X,Symbol}, x, :f)` +- `@invoke (x::X).f = v::V` expands to `invoke(setproperty!, Tuple{X,Symbol,V}, x, :f, v)` +- `@invoke (xs::Xs)[i::I]` expands to `invoke(getindex, Tuple{Xs,I}, xs, i)` +- `@invoke (xs::Xs)[i::I] = v::V` expands to `invoke(setindex!, Tuple{Xs,V,I}, xs, v, i)` + +# Examples + +```jldoctest +julia> @macroexpand @invoke f(x::T, y) +:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y)) + +julia> @invoke 420::Integer % Unsigned +0x00000000000001a4 + +julia> @macroexpand @invoke (x::X).f +:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f)) + +julia> @macroexpand @invoke (x::X).f = v::V +:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] +:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V +:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i)) +``` + +!!! compat "Julia 1.7" + This macro requires Julia 1.7 or later. + +!!! compat "Julia 1.9" + This macro is exported as of Julia 1.9. + +!!! compat "Julia 1.10" + The additional syntax is supported as of Julia 1.10. +""" +macro invoke(ex) + topmod = Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) + types = Expr(:curly, :Tuple) + out = Expr(:call, GlobalRef(Core, :invoke)) + isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) + push!(out.args, f) + push!(out.args, types) + for arg in args + if isexpr(arg, :(::)) + push!(out.args, arg.args[1]) + push!(types.args, arg.args[2]) + else + push!(out.args, arg) + push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg)) + end + end + return esc(out) +end + +""" + @invokelatest f(args...; kwargs...) + +Provides a convenient way to call [`invokelatest`](@ref). +`@invokelatest f(args...; kwargs...)` will simply be expanded into +`Base.invokelatest(f, args...; kwargs...)`. + +It also supports the following syntax: +- `@invokelatest x.f` expands to `Base.invokelatest(getproperty, x, :f)` +- `@invokelatest x.f = v` expands to `Base.invokelatest(setproperty!, x, :f, v)` +- `@invokelatest xs[i]` expands to `Base.invokelatest(getindex, xs, i)` +- `@invokelatest xs[i] = v` expands to `Base.invokelatest(setindex!, xs, v, i)` + +```jldoctest +julia> @macroexpand @invokelatest f(x; kw=kwv) +:(Base.invokelatest(f, x; kw = kwv)) + +julia> @macroexpand @invokelatest x.f +:(Base.invokelatest(Base.getproperty, x, :f)) + +julia> @macroexpand @invokelatest x.f = v +:(Base.invokelatest(Base.setproperty!, x, :f, v)) + +julia> @macroexpand @invokelatest xs[i] +:(Base.invokelatest(Base.getindex, xs, i)) + +julia> @macroexpand @invokelatest xs[i] = v +:(Base.invokelatest(Base.setindex!, xs, v, i)) +``` + +!!! compat "Julia 1.7" + This macro requires Julia 1.7 or later. + +!!! compat "Julia 1.9" + Prior to Julia 1.9, this macro was not exported, and was called as `Base.@invokelatest`. + +!!! compat "Julia 1.10" + The additional `x.f` and `xs[i]` syntax requires Julia 1.10. +""" +macro invokelatest(ex) + topmod = Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) + out = Expr(:call, GlobalRef(Base, :invokelatest)) + isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) + push!(out.args, f) + append!(out.args, args) + return esc(out) +end + +function destructure_callex(topmod::Module, @nospecialize(ex)) + function flatten(xs) + out = Any[] + for x in xs + if isexpr(x, :tuple) + append!(out, x.args) + else + push!(out, x) + end + end + return out + end + + kwargs = Any[] + if isexpr(ex, :call) # `f(args...)` + f = first(ex.args) + args = Any[] + for x in ex.args[2:end] + if isexpr(x, :parameters) + append!(kwargs, x.args) + elseif isexpr(x, :kw) + push!(kwargs, x) + else + push!(args, x) + end + end + elseif isexpr(ex, :.) # `x.f` + f = GlobalRef(topmod, :getproperty) + args = flatten(ex.args) + elseif isexpr(ex, :ref) # `x[i]` + f = GlobalRef(topmod, :getindex) + args = flatten(ex.args) + elseif isexpr(ex, :(=)) # `x.f = v` or `x[i] = v` + lhs, rhs = ex.args + if isexpr(lhs, :.) + f = GlobalRef(topmod, :setproperty!) + args = flatten(Any[lhs.args..., rhs]) + elseif isexpr(lhs, :ref) + f = GlobalRef(topmod, :setindex!) + args = flatten(Any[lhs.args[1], rhs, lhs.args[2]]) + else + throw(ArgumentError("expected a `setproperty!` expression `x.f = v` or `setindex!` expression `x[i] = v`")) + end + else + throw(ArgumentError("expected a `:call` expression `f(args...; kwargs...)`")) + end + return f, args, kwargs +end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index e3b1ac499e9868..71f9da04baa4ae 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2647,7 +2647,7 @@ g26826(x) = getfield26826(x, :a, :b) # If this test is broken (especially if inference is getting a correct, but loose result, # like a Union) then it's potentially an indication that the optimizer isn't hitting the # InferenceResult cache properly for varargs methods. -let ct = Core.Compiler.code_typed(f26826, (Float64,))[1] +let ct = code_typed(f26826, (Float64,))[1] typed_code, retty = ct.first, ct.second found_poorly_typed_getfield_call = false for i = 1:length(typed_code.code)