From c6d5c2803a63864693ad5a057ca73f891731640f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 16 Oct 2024 08:52:24 +0000 Subject: [PATCH] Split reflection into compiler-dependent and compiler-independent pieces The `reflection.jl` file provides a large amount of functionality covering everything from helpers for access to core runtime data structures to setting up particular inference problems. It is included by both Base and Core.Compiler, but the functions that use the compiler, don't really make sense in the latter. In preparation for #56128, and stop including the compiler-dependent pieces in Core.Compiler. --- base/Base.jl | 1 + base/compiler/typeutils.jl | 2 - base/compiler/utilities.jl | 84 -- base/expr.jl | 14 + base/reflection.jl | 1720 ++++-------------------------------- base/reflection2.jl | 1466 ++++++++++++++++++++++++++++++ test/compiler/inference.jl | 2 +- 7 files changed, 1645 insertions(+), 1644 deletions(-) create mode 100644 base/reflection2.jl 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)