Skip to content

Commit ee5db5a

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) = invoke(Core.kwcall, $m, (;base), string, i) end julia> f(20,16) "14"
1 parent 4280731 commit ee5db5a

File tree

7 files changed

+77
-39
lines changed

7 files changed

+77
-39
lines changed

Compiler/src/abstractinterpretation.jl

Lines changed: 34 additions & 23 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 0 deletions
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/docs/basedocs.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,21 +2030,32 @@ 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). However, because this means
2041+
the runtime must do more work, `invoke` is generally also slower--sometimes significantly
2042+
so--than doing normal dispatch with a regular call.
20402043
2041-
Be careful when using `invoke` for functions that you don't write. What definition is used
2044+
Be careful when using `invoke` for functions that you don't write. What definition is used
20422045
for given `argtypes` is an implementation detail unless the function is explicitly states
20432046
that calling with certain `argtypes` is a part of public API. For example, the change
20442047
between `f1` and `f2` in the example below is usually considered compatible because the
20452048
change is invisible by the caller with a normal (non-`invoke`) call. However, the change is
20462049
visible if you use `invoke`.
20472050
2051+
# Passing a `Method` instead of a signature
2052+
The `argtypes` argument may be a `Method`, in which case the ordinary method table lookup is
2053+
bypassed entirely and the given method is invoked directly. Needing this feature is uncommon.
2054+
Note in particular that the specified `Method` may be entirely unreachable from ordinary dispatch
2055+
(or ordinary invoke), e.g. because it was replaced or fully covered by more specific methods.
2056+
If the method is part of the ordinary method table, this call behaves similar
2057+
to `invoke(f, method.sig, args...)`.
2058+
20482059
# Examples
20492060
```jldoctest
20502061
julia> f(x::Real) = x^2;

src/builtins.c

Lines changed: 19 additions & 11 deletions
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 ------------------------------------------

test/core.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8352,3 +8352,10 @@ macro define_call(sym)
83528352
end
83538353
@test eval(Expr(:toplevel, :(@define_call(f_macro_defined1)))) == 1
83548354
@test @define_call(f_macro_defined2) == 1
8355+
8356+
let m = which(+, (Int, Int))
8357+
@eval f56692(i) = invoke(+, $m, i, 4)
8358+
global g56692() = f56692(5) == 9 ? "true" : false
8359+
end
8360+
@test @inferred(f56692(3)) == 7
8361+
@test @inferred(g56692()) == "true"

0 commit comments

Comments
 (0)