Skip to content

Commit 9d81f63

Browse files
committed
support passing a specific Method to invoke
The main purpose of this is integrating with external compilers using overlay method tables, where the Method might need to come from a source other than regular dispatch. Note that this is generally much less well optimized at runtime than generic dispatch, so this shouldn't be treated as expecting to give a performance boost in any dynamic cases. Trivial examples: julia> let m = which(+, (Int, Int)) @eval f(i, j) = invoke(+, $m, i, j) end julia> f(2,2) 4 julia> let m = which(Core.kwcall, (@NamedTuple{base::Int}, typeof(string), Int)) @eval f(i, base) = @noinline invoke(string, $m, i; base) end julia> f(20,16) "14"
1 parent 5fab51a commit 9d81f63

File tree

7 files changed

+66
-39
lines changed

7 files changed

+66
-39
lines changed

Compiler/src/abstractinterpretation.jl

+34-23
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,7 @@ end
856856

857857
struct InvokeCall
858858
types # ::Type
859-
lookupsig # ::Type
860-
InvokeCall(@nospecialize(types), @nospecialize(lookupsig)) = new(types, lookupsig)
859+
InvokeCall(@nospecialize(types)) = new(types)
861860
end
862861

863862
struct ConstCallResult
@@ -2218,34 +2217,46 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
22182217
ft′ = argtype_by_index(argtypes, 2)
22192218
ft = widenconst(ft′)
22202219
ft === Bottom && return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()))
2221-
(types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false)
2222-
isexact || return Future(CallMeta(Any, Any, Effects(), NoCallInfo()))
2223-
unwrapped = unwrap_unionall(types)
2224-
types === Bottom && return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()))
2225-
if !(unwrapped isa DataType && unwrapped.name === Tuple.name)
2226-
return Future(CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()))
2227-
end
2228-
argtype = argtypes_to_type(argtype_tail(argtypes, 4))
2229-
nargtype = typeintersect(types, argtype)
2230-
nargtype === Bottom && return Future(CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()))
2231-
nargtype isa DataType || return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) # other cases are not implemented below
2232-
isdispatchelem(ft) || return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below
2233-
ft = ft::DataType
2234-
lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type
2235-
nargtype = Tuple{ft, nargtype.parameters...}
2236-
argtype = Tuple{ft, argtype.parameters...}
2237-
matched, valid_worlds = findsup(lookupsig, method_table(interp))
2238-
matched === nothing && return Future(CallMeta(Any, Any, Effects(), NoCallInfo()))
2239-
update_valid_age!(sv, valid_worlds)
2240-
method = matched.method
2220+
types = argtype_by_index(argtypes, 3)
2221+
if types isa Const && types.val isa Method
2222+
method = types.val::Method
2223+
types = method # argument value
2224+
lookupsig = method.sig # edge kind
2225+
argtype = argtypes_to_type(pushfirst!(argtype_tail(argtypes, 4), ft))
2226+
nargtype = typeintersect(lookupsig, argtype)
2227+
nargtype === Bottom && return Future(CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()))
2228+
nargtype isa DataType || return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) # other cases are not implemented below
2229+
else
2230+
widenconst(types) >: Method && return Future(CallMeta(Any, Any, Effects(), NoCallInfo()))
2231+
(types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false)
2232+
isexact || return Future(CallMeta(Any, Any, Effects(), NoCallInfo()))
2233+
unwrapped = unwrap_unionall(types)
2234+
types === Bottom && return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()))
2235+
if !(unwrapped isa DataType && unwrapped.name === Tuple.name)
2236+
return Future(CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()))
2237+
end
2238+
argtype = argtypes_to_type(argtype_tail(argtypes, 4))
2239+
nargtype = typeintersect(types, argtype)
2240+
nargtype === Bottom && return Future(CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()))
2241+
nargtype isa DataType || return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) # other cases are not implemented below
2242+
isdispatchelem(ft) || return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below
2243+
ft = ft::DataType
2244+
lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type
2245+
nargtype = Tuple{ft, nargtype.parameters...}
2246+
argtype = Tuple{ft, argtype.parameters...}
2247+
matched, valid_worlds = findsup(lookupsig, method_table(interp))
2248+
matched === nothing && return Future(CallMeta(Any, Any, Effects(), NoCallInfo()))
2249+
update_valid_age!(sv, valid_worlds)
2250+
method = matched.method
2251+
end
22412252
tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector
22422253
ti = tienv[1]
22432254
env = tienv[2]::SimpleVector
22442255
mresult = abstract_call_method(interp, method, ti, env, false, si, sv)::Future
22452256
match = MethodMatch(ti, env, method, argtype <: method.sig)
22462257
ft′_box = Core.Box(ft′)
22472258
lookupsig_box = Core.Box(lookupsig)
2248-
invokecall = InvokeCall(types, lookupsig)
2259+
invokecall = InvokeCall(types)
22492260
return Future{CallMeta}(mresult, interp, sv) do result, interp, sv
22502261
(; rt, exct, effects, edge, volatile_inf_result) = result
22512262
local ft′ = ft′_box.contents

Compiler/src/abstractlattice.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ end
229229
if isa(t, Const)
230230
# don't consider mutable values useful constants
231231
val = t.val
232-
return isa(val, Symbol) || isa(val, Type) || !ismutable(val)
232+
return isa(val, Symbol) || isa(val, Type) || isa(val, Method) || !ismutable(val)
233233
end
234234
isa(t, PartialTypeVar) && return false # this isn't forwardable
235235
return is_const_prop_profitable_arg(widenlattice(𝕃), t)

Compiler/src/utilities.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ function count_const_size(@nospecialize(x), count_self::Bool = true)
5454
# No definite size
5555
(isa(x, GenericMemory) || isa(x, String) || isa(x, SimpleVector)) &&
5656
return MAX_INLINE_CONST_SIZE + 1
57-
if isa(x, Module)
58-
# We allow modules, because we already assume they are externally
57+
if isa(x, Module) || isa(x, Method)
58+
# We allow modules and methods, because we already assume they are externally
5959
# rooted, so we count their contents as 0 size.
6060
return sizeof(Ptr{Cvoid})
6161
end

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ New library features
119119
* `Base.require_one_based_indexing` and `Base.has_offset_axes` are now public ([#56196])
120120
* New `ltruncate`, `rtruncate` and `ctruncate` functions for truncating strings to text width, accounting for char widths ([#55351])
121121
* `isless` (and thus `cmp`, sorting, etc.) is now supported for zero-dimensional `AbstractArray`s ([#55772])
122+
* `invoke` now supports passing a Method instead of a type signature making this interface somewhat more flexible for certain uncommon use cases ([#TBD]).
122123

123124
Standard library changes
124125
------------------------

base/Base_compiler.jl

+5
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ function Core.kwcall(kwargs::NamedTuple, ::typeof(invoke), f, T, args...)
185185
T = rewrap_unionall(Tuple{Core.Typeof(kwargs), Core.Typeof(f), (unwrap_unionall(T)::DataType).parameters...}, T)
186186
return invoke(Core.kwcall, T, kwargs, f, args...)
187187
end
188+
# for m::Method however, assume the user already looked it up correctly for kwargs but just needs the syntax rewrite to swap the positions of the invoke, kwcall, and other arguments
189+
function Core.kwcall(kwargs::NamedTuple, ::typeof(invoke), f, m::Method, args...)
190+
@inline
191+
return invoke(Core.kwcall, m, kwargs, f, args...)
192+
end
188193
# invoke does not have its own call cache, but kwcall for invoke does
189194
setfield!(typeof(invoke).name.mt, :max_args, 3, :monotonic) # invoke, f, T, args...
190195

base/docs/basedocs.jl

+4-2
Original file line numberDiff line numberDiff line change
@@ -2030,15 +2030,17 @@ applicable
20302030

20312031
"""
20322032
invoke(f, argtypes::Type, args...; kwargs...)
2033+
invoke(f, argtypes::Method, args...; kwargs...)
20332034
20342035
Invoke a method for the given generic function `f` matching the specified types `argtypes` on the
20352036
specified arguments `args` and passing the keyword arguments `kwargs`. The arguments `args` must
20362037
conform with the specified types in `argtypes`, i.e. conversion is not automatically performed.
20372038
This method allows invoking a method other than the most specific matching method, which is useful
20382039
when the behavior of a more general definition is explicitly needed (often as part of the
2039-
implementation of a more specific method of the same function).
2040+
implementation of a more specific method of the same function). This method also allows
2041+
specifying an exact Method to execute, similar to having passed `method.sig` as `argtypes`.
20402042
2041-
Be careful when using `invoke` for functions that you don't write. What definition is used
2043+
Be careful when using `invoke` for functions that you don't write. What definition is used
20422044
for given `argtypes` is an implementation detail unless the function is explicitly states
20432045
that calling with certain `argtypes` is a part of public API. For example, the change
20442046
between `f1` and `f2` in the example below is usually considered compatible because the

src/builtins.c

+19-11
Original file line numberDiff line numberDiff line change
@@ -931,22 +931,27 @@ JL_CALLABLE(jl_f__call_in_world_total)
931931

932932
// tuples ---------------------------------------------------------------------
933933

934-
JL_CALLABLE(jl_f_tuple)
934+
static jl_value_t *arg_tuple(jl_value_t *a1, jl_value_t **args, size_t nargs)
935935
{
936936
size_t i;
937-
if (nargs == 0)
938-
return (jl_value_t*)jl_emptytuple;
939-
jl_datatype_t *tt = jl_inst_arg_tuple_type(args[0], &args[1], nargs, 0);
937+
jl_datatype_t *tt = jl_inst_arg_tuple_type(a1, args, nargs, 0);
940938
JL_GC_PROMISE_ROOTED(tt); // it is a concrete type
941939
if (tt->instance != NULL)
942940
return tt->instance;
943941
jl_task_t *ct = jl_current_task;
944942
jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(tt), tt);
945943
for (i = 0; i < nargs; i++)
946-
set_nth_field(tt, jv, i, args[i], 0);
944+
set_nth_field(tt, jv, i, i == 0 ? a1 : args[i - 1], 0);
947945
return jv;
948946
}
949947

948+
JL_CALLABLE(jl_f_tuple)
949+
{
950+
if (nargs == 0)
951+
return (jl_value_t*)jl_emptytuple;
952+
return arg_tuple(args[0], &args[1], nargs);
953+
}
954+
950955
JL_CALLABLE(jl_f_svec)
951956
{
952957
size_t i;
@@ -1577,14 +1582,17 @@ JL_CALLABLE(jl_f_invoke)
15771582
{
15781583
JL_NARGSV(invoke, 2);
15791584
jl_value_t *argtypes = args[1];
1580-
JL_GC_PUSH1(&argtypes);
1581-
if (!jl_is_tuple_type(jl_unwrap_unionall(args[1])))
1582-
jl_type_error("invoke", (jl_value_t*)jl_anytuple_type_type, args[1]);
1585+
if (jl_is_method(argtypes)) {
1586+
jl_method_t *m = (jl_method_t*)argtypes;
1587+
if (!jl_tuple1_isa(args[0], &args[2], nargs - 1, (jl_datatype_t*)m->sig))
1588+
jl_type_error("invoke: argument type error", argtypes, arg_tuple(args[0], &args[2], nargs - 1));
1589+
return jl_gf_invoke_by_method(m, args[0], &args[2], nargs - 1);
1590+
}
1591+
if (!jl_is_tuple_type(jl_unwrap_unionall(argtypes)))
1592+
jl_type_error("invoke", (jl_value_t*)jl_anytuple_type_type, argtypes);
15831593
if (!jl_tuple_isa(&args[2], nargs - 2, (jl_datatype_t*)argtypes))
15841594
jl_type_error("invoke: argument type error", argtypes, jl_f_tuple(NULL, &args[2], nargs - 2));
1585-
jl_value_t *res = jl_gf_invoke(argtypes, args[0], &args[2], nargs - 1);
1586-
JL_GC_POP();
1587-
return res;
1595+
return jl_gf_invoke(argtypes, args[0], &args[2], nargs - 1);
15881596
}
15891597

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

0 commit comments

Comments
 (0)