Skip to content

Commit

Permalink
remove kwfuncs from Julia (JuliaLang#47157)
Browse files Browse the repository at this point in the history
* remove kwfuncs from Julia

These kwsorter methods only exist for dispatch, which means they do not
need to be unique. We can optionally have the primary MethodTable
contain an extra kwtable::MethodTable field, if this turns out to be
slow, since this already introduces the concept of
`jl_kwmethod_table_for` (for better reflection and max_args).

* remove jl_f_kwinvoke builtin, reimplement in Julia

This instantly grants total inference and inlining support, where
previously this was a completely opaque call!
  • Loading branch information
vtjnash committed Oct 16, 2022
1 parent 97d86f8 commit ccb0a02
Show file tree
Hide file tree
Showing 41 changed files with 184 additions and 300 deletions.
11 changes: 11 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ include("generator.jl")
include("reflection.jl")
include("options.jl")

# define invoke(f, T, args...; kwargs...), without kwargs wrapping
# to forward to invoke
function Core.kwcall(kwargs, ::typeof(invoke), f, T, args...)
@inline
# prepend kwargs and f to the invoked from the user
T = rewrap_unionall(Tuple{Any, Core.Typeof(f), (unwrap_unionall(T)::DataType).parameters...}, T)
return invoke(Core.kwcall, T, kwargs, f, args...)
end
# invoke does not have its own call cache, but kwcall for invoke does
typeof(invoke).name.mt.max_args = 3 # invoke, f, T, args...

# core operations & types
include("promotion.jl")
include("tuple.jl")
Expand Down
8 changes: 5 additions & 3 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,11 @@ include(m::Module, fname::String) = ccall(:jl_load_, Any, (Any, Any), m, fname)

eval(m::Module, @nospecialize(e)) = ccall(:jl_toplevel_eval_in, Any, (Any, Any), m, e)

kwfunc(@nospecialize(f)) = ccall(:jl_get_keyword_sorter, Any, (Any,), f)

kwftype(@nospecialize(t)) = typeof(ccall(:jl_get_kwsorter, Any, (Any,), t))
# dispatch token indicating a kwarg (keyword sorter) call
function kwcall end
# deprecated internal functions:
kwfunc(@nospecialize(f)) = kwcall
kwftype(@nospecialize(t)) = typeof(kwcall)

mutable struct Box
contents::Any
Expand Down
11 changes: 0 additions & 11 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1754,17 +1754,6 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
elseif isa(f, Core.OpaqueClosure)
# calling an OpaqueClosure about which we have no information returns no information
return CallMeta(Any, Effects(), NoCallInfo())
elseif f === Core.kwfunc
if la == 2
aty = argtypes[2]
if !isvarargtype(aty)
ft = widenconst(aty)
if isa(ft, DataType) && isdefined(ft.name, :mt) && isdefined(ft.name.mt, :kwsorter)
return CallMeta(Const(ft.name.mt.kwsorter), EFFECTS_TOTAL, MethodResultPure())
end
end
end
return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo())
elseif f === TypeVar
# Manually look through the definition of TypeVar to
# make sure to be able to get `PartialTypeVar`s out.
Expand Down
6 changes: 1 addition & 5 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1877,9 +1877,6 @@ function _builtin_nothrow(@specialize(lattice::AbstractLattice), @nospecialize(f
elseif f === Core.sizeof
length(argtypes) == 1 || return false
return sizeof_nothrow(argtypes[1])
elseif f === Core.kwfunc
length(argtypes) == 1 || return false
return isa(rt, Const)
elseif f === Core.ifelse
length(argtypes) == 3 || return false
return argtypes[1] ₗ Bool
Expand Down Expand Up @@ -1919,7 +1916,7 @@ const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]
const _EFFECT_FREE_BUILTINS = [
fieldtype, apply_type, isa, UnionAll,
getfield, arrayref, const_arrayref, isdefined, Core.sizeof,
Core.kwfunc, Core.ifelse, Core._typevar, (<:),
Core.ifelse, Core._typevar, (<:),
typeassert, throw, arraysize, getglobal, compilerbarrier
]

Expand All @@ -1934,7 +1931,6 @@ const _CONSISTENT_BUILTINS = Any[
isa,
UnionAll,
Core.sizeof,
Core.kwfunc,
Core.ifelse,
(<:),
typeassert,
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ end

function get_compileable_sig(method::Method, @nospecialize(atype), sparams::SimpleVector)
isa(atype, DataType) || return nothing
mt = ccall(:jl_method_table_for, Any, (Any,), atype)
mt = ccall(:jl_method_get_table, Any, (Any,), method)
mt === nothing && return nothing
return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any),
mt, atype, sparams, method)
Expand Down
7 changes: 2 additions & 5 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,8 @@ function firstcaller(bt::Vector, funcsyms)
if !found
li = lkup.linfo
if li isa Core.MethodInstance
ft = ccall(:jl_first_argument_datatype, Any, (Any,), (li.def::Method).sig)
if isType(ft)
ft = unwrap_unionall(ft.parameters[1])
found = (isa(ft, DataType) && ft.name.name in funcsyms)
end
def = li.def
found = def isa Method && def.name in funcsyms
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ end
## keyword arg lowering generates calls to this ##
function kwerr(kw, args::Vararg{Any,N}) where {N}
@noinline
throw(MethodError(typeof(args[1]).name.mt.kwsorter, (kw,args...)))
throw(MethodError(Core.kwcall, (kw, args...)))
end

## system error handling ##
Expand Down
27 changes: 16 additions & 11 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,16 @@ function showerror(io::IO, ex::MethodError)
show_candidates = true
print(io, "MethodError: ")
ft = typeof(f)
name = ft.name.mt.name
f_is_function = false
kwargs = ()
if endswith(string(ft.name.name), "##kw")
f = ex.args[2]
if f === Core.kwcall && !is_arg_types
f = (ex.args::Tuple)[2]
ft = typeof(f)
name = ft.name.mt.name
arg_types_param = arg_types_param[3:end]
kwargs = pairs(ex.args[1])
ex = MethodError(f, ex.args[3:end::Int])
end
name = ft.name.mt.name
if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types
f_is_function = true
show_convert_error(io, ex, arg_types_param)
Expand Down Expand Up @@ -794,11 +793,6 @@ function show_backtrace(io::IO, t::Vector)
end


function is_kw_sorter_name(name::Symbol)
sn = string(name)
return !startswith(sn, '#') && endswith(sn, "##kw")
end

# For improved user experience, filter out frames for include() implementation
# - see #33065. See also #35371 for extended discussion of internal frames.
function _simplify_include_frames(trace)
Expand Down Expand Up @@ -850,15 +844,26 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
continue
end

if (lkup.from_c && skipC) || is_kw_sorter_name(lkup.func)
if (lkup.from_c && skipC)
continue
end
code = lkup.linfo
if code isa MethodInstance
def = code.def
if def isa Method
if def.name === :kwcall && def.module === Core
continue
end
end
elseif !lkup.from_c
lkup.func === :kwcall && continue
end
count += 1
if count > limit
break
end

if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== lkup.linfo
if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== last_frame.linfo
if n > 0
push!(ret, (last_frame, n))
end
Expand Down
4 changes: 2 additions & 2 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ function invokelatest(@nospecialize(f), @nospecialize args...; kwargs...)
if isempty(kwargs)
return Core._call_latest(f, args...)
end
return Core._call_latest(Core.kwfunc(f), kwargs, f, args...)
return Core._call_latest(Core.kwcall, kwargs, f, args...)
end

"""
Expand Down Expand Up @@ -843,7 +843,7 @@ function invoke_in_world(world::UInt, @nospecialize(f), @nospecialize args...; k
if isempty(kwargs)
return Core._call_in_world(world, f, args...)
end
return Core._call_in_world(world, Core.kwfunc(f), kwargs, f, args...)
return Core._call_in_world(world, Core.kwcall, kwargs, f, args...)
end

inferencebarrier(@nospecialize(x)) = compilerbarrier(:type, x)
Expand Down
8 changes: 2 additions & 6 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,8 @@ end

# NOTE: second argument is deprecated and is no longer used
function kwarg_decl(m::Method, kwtype = nothing)
if m.sig === Tuple # OpaqueClosure
return Symbol[]
end
mt = get_methodtable(m)
if isdefined(mt, :kwsorter)
kwtype = typeof(mt.kwsorter)
if m.sig !== Tuple # OpaqueClosure or Builtin
kwtype = typeof(Core.kwcall)
sig = rewrap_unionall(Tuple{kwtype, Any, (unwrap_unionall(m.sig)::DataType).parameters...}, m.sig)
kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, get_world_counter())
if kwli !== nothing
Expand Down
4 changes: 2 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ function to_tuple_type(@nospecialize(t))
if isa(t, Type) && t <: Tuple
for p in unwrap_unionall(t).parameters
if isa(p, Core.TypeofVararg)
p = p.T
p = unwrapva(p)
end
if !(isa(p, Type) || isa(p, TypeVar))
error("argument tuple type must contain only types")
Expand Down Expand Up @@ -1804,7 +1804,7 @@ function delete_method(m::Method)
end

function get_methodtable(m::Method)
return ccall(:jl_method_table_for, Any, (Any,), m.sig)::Core.MethodTable
return ccall(:jl_method_get_table, Any, (Any,), m)::Core.MethodTable
end

"""
Expand Down
1 change: 0 additions & 1 deletion contrib/generate_precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ if Test !== nothing
precompile(Tuple{typeof(Test.match_logs), Function, Tuple{String, Regex}})
precompile(Tuple{typeof(Base.CoreLogging.shouldlog), Test.TestLogger, Base.CoreLogging.LogLevel, Module, Symbol, Symbol})
precompile(Tuple{typeof(Base.CoreLogging.handle_message), Test.TestLogger, Base.CoreLogging.LogLevel, String, Module, Symbol, Symbol, String, Int})
precompile(Tuple{typeof(Core.kwfunc(Base.CoreLogging.handle_message)), typeof((exception=nothing,)), typeof(Base.CoreLogging.handle_message), Test.TestLogger, Base.CoreLogging.LogLevel, String, Module, Symbol, Symbol, String, Int})
precompile(Tuple{typeof(Test.detect_ambiguities), Any})
precompile(Tuple{typeof(Test.collect_test_logs), Function})
precompile(Tuple{typeof(Test.do_broken_test), Test.ExecutionResult, Any})
Expand Down
12 changes: 6 additions & 6 deletions doc/src/devdocs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs);

Given the above dispatch process, conceptually all that is needed to add a new method is (1) a
tuple type, and (2) code for the body of the method. `jl_method_def` implements this operation.
`jl_first_argument_datatype` is called to extract the relevant method table from what would be
`jl_method_table_for` is called to extract the relevant method table from what would be
the type of the first argument. This is much more complicated than the corresponding procedure
during dispatch, since the argument tuple type might be abstract. For example, we can define:

Expand Down Expand Up @@ -141,9 +141,9 @@ but works reasonably well.
## Keyword arguments
Keyword arguments work by associating a special, hidden function object with each method table
that has definitions with keyword arguments. This function is called the "keyword argument sorter"
or "keyword sorter", or "kwsorter", and is stored in the `kwsorter` field of `MethodTable` objects.
Keyword arguments work by adding methods to the kwcall function. This function
is usually the "keyword argument sorter" or "keyword sorter", which then calls
the inner body of the function (defined anonymously).
Every definition in the kwsorter function has the same arguments as some definition in the normal
method table, except with a single `NamedTuple` argument prepended, which gives
the names and values of passed keyword arguments. The kwsorter's job is to move keyword arguments
Expand Down Expand Up @@ -220,10 +220,10 @@ circle((0,0), 1.0, color = red; other...)
is lowered to:
```julia
kwfunc(circle)(merge((color = red,), other), circle, (0,0), 1.0)
kwcall(merge((color = red,), other), circle, (0,0), 1.0)
```
`kwfunc` (also in`Core`) fetches the kwsorter for the called function.
`kwcall` (also in`Core`) denotes a kwcall signature and dispatch.
The keyword splatting operation (written as `other...`) calls the named tuple `merge` function.
This function further unpacks each *element* of `other`, expecting each one to contain two values
(a symbol and a value).
Expand Down
2 changes: 1 addition & 1 deletion doc/src/devdocs/locks.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ These data structures each need locks due to being shared mutable global state.
list for the above lock priority list. This list does not include level 1 leaf resources due to
their simplicity.

MethodTable modifications (def, cache, kwsorter type) : MethodTable->writelock
MethodTable modifications (def, cache) : MethodTable->writelock

Type declarations : toplevel lock

Expand Down
1 change: 0 additions & 1 deletion doc/src/devdocs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ TypeName
defs: Nothing nothing
cache: Nothing nothing
max_args: Int64 0
kwsorter: #undef
module: Module Core
: Int64 0
: Int64 0
Expand Down
6 changes: 0 additions & 6 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,6 @@ DECLARE_BUILTIN(finalizer);
DECLARE_BUILTIN(_compute_sparams);
DECLARE_BUILTIN(_svec_ref);

JL_CALLABLE(jl_f_invoke_kwsorter);
#ifdef DEFINE_BUILTIN_GLOBALS
JL_DLLEXPORT jl_fptr_args_t jl_f_invoke_kwsorter_addr = &jl_f_invoke_kwsorter;
#else
JL_DLLEXPORT extern jl_fptr_args_t jl_f_invoke_kwsorter_addr;
#endif
JL_CALLABLE(jl_f__structtype);
JL_CALLABLE(jl_f__abstracttype);
JL_CALLABLE(jl_f__primitivetype);
Expand Down
49 changes: 0 additions & 49 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -1359,50 +1359,6 @@ JL_CALLABLE(jl_f_invoke)
return res;
}

JL_CALLABLE(jl_f_invoke_kwsorter)
{
JL_NARGSV(invoke, 3);
jl_value_t *kwargs = args[0];
// args[1] is `invoke` itself
jl_value_t *func = args[2];
jl_value_t *argtypes = args[3];
jl_value_t *kws = jl_get_keyword_sorter(func);
JL_GC_PUSH1(&argtypes);
if (jl_is_tuple_type(argtypes)) {
// construct a tuple type for invoking a keyword sorter by putting the kw container type
// and the type of the function at the front.
size_t i, nt = jl_nparams(argtypes) + 2;
if (nt < jl_page_size/sizeof(jl_value_t*)) {
jl_value_t **types = (jl_value_t**)alloca(nt*sizeof(jl_value_t*));
types[0] = (jl_value_t*)jl_namedtuple_type;
types[1] = jl_is_type(func) ? (jl_value_t*)jl_wrap_Type(func) : jl_typeof(func);
for (i = 2; i < nt; i++)
types[i] = jl_tparam(argtypes, i - 2);
argtypes = (jl_value_t*)jl_apply_tuple_type_v(types, nt);
}
else {
jl_svec_t *types = jl_alloc_svec_uninit(nt);
JL_GC_PUSH1(&types);
jl_svecset(types, 0, jl_namedtuple_type);
jl_svecset(types, 1, jl_is_type(func) ? (jl_value_t*)jl_wrap_Type(func) : jl_typeof(func));
for (i = 2; i < nt; i++)
jl_svecset(types, i, jl_tparam(argtypes, i - 2));
argtypes = (jl_value_t*)jl_apply_tuple_type(types);
JL_GC_POP();
}
}
else {
// invoke will throw an error
}
args[0] = kws;
args[1] = argtypes;
args[2] = kwargs;
args[3] = func;
jl_value_t *res = jl_f_invoke(NULL, args, nargs);
JL_GC_POP();
return res;
}

// Expr constructor for internal use ------------------------------------------

jl_expr_t *jl_exprn(jl_sym_t *head, size_t n)
Expand Down Expand Up @@ -2011,11 +1967,6 @@ void jl_init_primitives(void) JL_GC_DISABLED
// method table utils
jl_builtin_applicable = add_builtin_func("applicable", jl_f_applicable);
jl_builtin_invoke = add_builtin_func("invoke", jl_f_invoke);
jl_typename_t *itn = ((jl_datatype_t*)jl_typeof(jl_builtin_invoke))->name;
jl_value_t *ikws = jl_new_generic_function_with_supertype(itn->name, jl_core_module, jl_builtin_type);
itn->mt->kwsorter = ikws;
jl_gc_wb(itn->mt, ikws);
jl_mk_builtin_func((jl_datatype_t*)jl_typeof(ikws), jl_symbol_name(jl_gf_name(ikws)), jl_f_invoke_kwsorter);

// internal functions
jl_builtin_apply_type = add_builtin_func("apply_type", jl_f_apply_type);
Expand Down
1 change: 0 additions & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,6 @@ static const auto &builtin_func_map() {
{ jl_f_svec_addr, new JuliaFunction{XSTR(jl_f_svec), get_func_sig, get_func_attrs} },
{ jl_f_applicable_addr, new JuliaFunction{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} },
{ jl_f_invoke_addr, new JuliaFunction{XSTR(jl_f_invoke), get_func_sig, get_func_attrs} },
{ jl_f_invoke_kwsorter_addr, new JuliaFunction{XSTR(jl_f_invoke_kwsorter), get_func_sig, get_func_attrs} },
{ jl_f_isdefined_addr, new JuliaFunction{XSTR(jl_f_isdefined), get_func_sig, get_func_attrs} },
{ jl_f_getfield_addr, new JuliaFunction{XSTR(jl_f_getfield), get_func_sig, get_func_attrs} },
{ jl_f_setfield_addr, new JuliaFunction{XSTR(jl_f_setfield), get_func_sig, get_func_attrs} },
Expand Down
2 changes: 1 addition & 1 deletion src/common_symbols1.inc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jl_symbol("toInt64"),
jl_symbol("arraylen"),
jl_symbol("typeassert"),
jl_symbol("map"),
jl_symbol("kwfunc"),
jl_symbol("kwcall"),
jl_symbol("ArgumentError"),
jl_symbol("lshr_int"),
jl_symbol("axes"),
Expand Down
1 change: 0 additions & 1 deletion src/datatype.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo
jl_atomic_store_relaxed(&mt->leafcache, (jl_array_t*)jl_an_empty_vec_any);
jl_atomic_store_relaxed(&mt->cache, jl_nothing);
mt->max_args = 0;
mt->kwsorter = NULL;
mt->backedges = NULL;
JL_MUTEX_INIT(&mt->writelock);
mt->offs = 0;
Expand Down
Loading

0 comments on commit ccb0a02

Please sign in to comment.