Skip to content

Commit

Permalink
gf: make Builtins more like regular functions
Browse files Browse the repository at this point in the history
Now we can fully handle all type signatures in ml_matches and give back
the complete list (though potentially slowly).
  • Loading branch information
vtjnash committed Jan 24, 2022
1 parent 295cee5 commit 4ab81fc
Show file tree
Hide file tree
Showing 20 changed files with 252 additions and 213 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Compiler/Runtime improvements
calls ([#43239]).
* Abstract callsite can now be inlined or statically resolved as far as the callsite has a single
matching method ([#43113]).
* Builtin function are now a bit more like generic functions, and can be enumerated with `methods` ([#43865]).

Command-line option changes
---------------------------
Expand Down
28 changes: 22 additions & 6 deletions base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -958,20 +958,36 @@ function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance
return src
end

function return_type(@nospecialize(f), @nospecialize(t))
function return_type(@nospecialize(f), t::DataType) # this method has a special tfunc
world = ccall(:jl_get_tls_world_age, UInt, ())
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), Any[_return_type, f, t, world], 4)
args = Any[_return_type, NativeInterpreter(world), Tuple{Core.Typeof(f), t.parameters...}]
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args))
end

_return_type(@nospecialize(f), @nospecialize(t), world) = _return_type(NativeInterpreter(world), f, t)
function return_type(@nospecialize(f), t::DataType, world::UInt)
return return_type(Tuple{Core.Typeof(f), t.parameters...}, world)
end

function return_type(t::DataType)
world = ccall(:jl_get_tls_world_age, UInt, ())
return return_type(t, world)
end

function return_type(t::DataType, world::UInt)
args = Any[_return_type, NativeInterpreter(world), t]
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args))
end

function _return_type(interp::AbstractInterpreter, @nospecialize(f), @nospecialize(t))
function _return_type(interp::AbstractInterpreter, t::DataType)
rt = Union{}
f = singleton_type(t.parameters[1])
if isa(f, Builtin)
rt = builtin_tfunction(interp, f, Any[t.parameters...], nothing)
args = Any[t.parameters...]
popfirst!(args)
rt = builtin_tfunction(interp, f, args, nothing)
rt = widenconst(rt)
else
for match in _methods(f, t, -1, get_world_counter(interp))::Vector
for match in _methods_by_ftype(t, -1, get_world_counter(interp))::Vector
match = match::MethodMatch
ty = typeinf_type(interp, match.method, match.spec_types, match.sparams)
ty === nothing && return Any
Expand Down
39 changes: 22 additions & 17 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ function show(io::IO, m::Method)
file, line = updated_methodloc(m)
print(io, " at ", file, ":", line)
end
nothing
end

function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
Expand All @@ -232,24 +233,27 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
hasname = isdefined(mt.module, name) &&
typeof(getfield(mt.module, name)) <: Function
n = length(ms)
if mt.module === Core && n == 0 && mt.defs === nothing && mt.cache !== nothing
# try to detect Builtin
print(io, "# built-in function; no methods")
m = n==1 ? "method" : "methods"
print(io, "# $n $m")
sname = string(name)
namedisplay = namefmt(sname)
if hasname
what = (startswith(sname, '@') ?
"macro"
: mt.module === Core && last(ms).sig === Tuple ?
"builtin function"
: # else
"generic function")
print(io, " for ", what, " ", namedisplay)
elseif '#' in sname
print(io, " for anonymous function ", namedisplay)
elseif mt === _TYPE_NAME.mt
print(io, " for type constructor")
else
m = n==1 ? "method" : "methods"
print(io, "# $n $m")
sname = string(name)
namedisplay = namefmt(sname)
if hasname
what = startswith(sname, '@') ? "macro" : "generic function"
print(io, " for ", what, " ", namedisplay)
elseif '#' in sname
print(io, " for anonymous function ", namedisplay)
elseif mt === _TYPE_NAME.mt
print(io, " for type constructor")
end
print(io, ":")
print(io, " for callable object")
end
n > 0 && print(io, ":")
nothing
end

function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true)
Expand All @@ -267,7 +271,7 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru
last_shown_line_infos === nothing || empty!(last_shown_line_infos)

for meth in ms
if max==-1 || n<max
if max == -1 || n < max
n += 1
println(io)
print(io, "[$n] ")
Expand All @@ -292,6 +296,7 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru
end
end
end
nothing
end

show(io::IO, ms::MethodList) = show_method_table(io, ms)
Expand Down
77 changes: 25 additions & 52 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -930,14 +930,6 @@ function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing
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{Array{Any,1}, Bool}
end

function _method_by_ftype(args...)
matches = _methods_by_ftype(args...)
if length(matches) != 1
error("no unique matching method found for the specified argument types")
end
return matches[1]
end

# high-level, more convenient method lookup functions

# type for reflecting and pretty-printing a subset of methods
Expand Down Expand Up @@ -973,9 +965,6 @@ See also: [`which`](@ref) and `@which`.
"""
function methods(@nospecialize(f), @nospecialize(t),
mod::Union{Tuple{Module},AbstractArray{Module},Nothing}=nothing)
if isa(f, Core.Builtin)
throw(ArgumentError("argument is not a generic function"))
end
t = to_tuple_type(t)
world = get_world_counter()
# Lack of specialization => a comprehension triggers too many invalidations via _collect, so collect the methods manually
Expand All @@ -988,15 +977,12 @@ function methods(@nospecialize(f), @nospecialize(t),
end
methods(@nospecialize(f), @nospecialize(t), mod::Module) = methods(f, t, (mod,))

methods(f::Core.Builtin) = MethodList(Method[], typeof(f).name.mt)

function methods_including_ambiguous(@nospecialize(f), @nospecialize(t))
tt = signature_type(f, t)
world = get_world_counter()
min = RefValue{UInt}(typemin(UInt))
max = RefValue{UInt}(typemax(UInt))
ms = _methods_by_ftype(tt, nothing, -1, world, true, min, max, Ptr{Int32}(C_NULL))
isa(ms, Bool) && return ms
ms = _methods_by_ftype(tt, nothing, -1, world, true, min, max, Ptr{Int32}(C_NULL))::Vector
return MethodList(Method[(m::Core.MethodMatch).method for m in ms], typeof(f).name.mt)
end

Expand Down Expand Up @@ -1213,9 +1199,6 @@ function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f));
debuginfo::Symbol=:default,
world = get_world_counter(),
interp = Core.Compiler.NativeInterpreter(world))
if isa(f, Core.Builtin)
throw(ArgumentError("argument is not a generic function"))
end
if isa(f, Core.OpaqueClosure)
return code_typed_opaque_closure(f; optimize, debuginfo, interp)
end
Expand Down Expand Up @@ -1262,18 +1245,18 @@ function code_typed_by_type(@nospecialize(tt#=::Type=#);
throw(ArgumentError("'debuginfo' must be either :source or :none"))
end
tt = to_tuple_type(tt)
matches = _methods_by_ftype(tt, -1, world)
if matches === false
error("signature does not correspond to a generic function")
end
matches = _methods_by_ftype(tt, -1, world)::Vector
asts = []
for match in matches::Vector
for match in matches
match = match::Core.MethodMatch
meth = func_for_method_checked(match.method, tt, match.sparams)
(code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, optimize)
code === nothing && error("inference not successful") # inference disabled?
debuginfo === :none && remove_linenums!(code)
push!(asts, code => ty)
if code === nothing
push!(asts, meth => Any)
else
debuginfo === :none && remove_linenums!(code)
push!(asts, code => ty)
end
end
return asts
end
Expand All @@ -1295,18 +1278,14 @@ end

function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)), interp=Core.Compiler.NativeInterpreter())
ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
if isa(f, Core.Builtin)
throw(ArgumentError("argument is not a generic function"))
end
types = to_tuple_type(types)
rt = []
world = get_world_counter()
for match in _methods(f, types, -1, world)::Vector
match = match::Core.MethodMatch
meth = func_for_method_checked(match.method, types, match.sparams)
ty = Core.Compiler.typeinf_type(interp, meth, match.spec_types, match.sparams)
ty === nothing && error("inference not successful") # inference disabled?
push!(rt, ty)
push!(rt, something(ty, Any))
end
return rt
end
Expand All @@ -1318,37 +1297,34 @@ 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...)
if isa(f, Core.Builtin)
throw(ArgumentError("argument is not a generic function"))
end
tt = signature_type(f, t)
print_statement_costs(io, tt; kwargs...)
end

function print_statement_costs(io::IO, @nospecialize(tt#=::Type=#);
world = get_world_counter(),
interp = Core.Compiler.NativeInterpreter(world))
matches = _methods_by_ftype(tt, -1, world)
if matches === false
error("signature does not correspond to a generic function")
end
matches = _methods_by_ftype(tt, -1, world)::Vector
params = Core.Compiler.OptimizationParams(interp)
cst = Int[]
for match in matches::Vector
for match in matches
match = match::Core.MethodMatch
meth = func_for_method_checked(match.method, tt, match.sparams)
(code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, true)
code === nothing && error("inference not successful") # inference disabled?
empty!(cst)
resize!(cst, length(code.code))
maxcost = Core.Compiler.statement_costs!(cst, code.code, code, Any[match.sparams...], false, params)
nd = ndigits(maxcost)
println(io, meth)
irshow_config = IRShow.IRShowConfig() do io, linestart, idx
print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ")
return ""
(code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, true)
if code === nothing
println(io, " inference not successful")
else
empty!(cst)
resize!(cst, length(code.code))
maxcost = Core.Compiler.statement_costs!(cst, code.code, code, Any[match.sparams...], false, 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
IRShow.show_ir(io, code, irshow_config)
println(io)
end
end
Expand Down Expand Up @@ -1377,9 +1353,6 @@ If `types` is an abstract type, then the method that would be called by `invoke`
See also: [`parentmodule`](@ref), and `@which` and `@edit` in [`InteractiveUtils`](@ref man-interactive-utils).
"""
function which(@nospecialize(f), @nospecialize(t))
if isa(f, Core.Builtin)
throw(ArgumentError("argument is not a generic function"))
end
t = to_tuple_type(t)
tt = signature_type(f, t)
return which(tt)
Expand Down
5 changes: 3 additions & 2 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -1771,8 +1771,9 @@ static void add_builtin(const char *name, jl_value_t *v)
jl_fptr_args_t jl_get_builtin_fptr(jl_value_t *b)
{
assert(jl_isa(b, (jl_value_t*)jl_builtin_type));
jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&jl_gf_mtable(b)->cache);
jl_code_instance_t *ci = jl_atomic_load_relaxed(&entry->func.linfo->cache);
jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&jl_gf_mtable(b)->defs);
jl_method_instance_t *mi = jl_atomic_load_relaxed(&entry->func.method->unspecialized);
jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache);
return jl_atomic_load_relaxed(&ci->specptr.fptr1);
}

Expand Down
Loading

0 comments on commit 4ab81fc

Please sign in to comment.