Skip to content

Commit

Permalink
Add Base.invoke_in_world and _apply_in_world builtin (#35844)
Browse files Browse the repository at this point in the history
New builtin `Core._apply_in_world` to allow frozen-world APIs to be
implemented in pure Julia code. Also add an internal, experimental API
`Base.invoke_in_world()` in analogy to `Base.invokelatest()` to make this
easier to use (especially for keyword args) and to give us a place to
put some documentation.
  • Loading branch information
c42f authored Jul 3, 2020
1 parent 604f658 commit f9496fb
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 1 deletion.
34 changes: 34 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,40 @@ function invokelatest(@nospecialize(f), @nospecialize args...; kwargs...)
Core._apply_latest(inner)
end

"""
invoke_in_world(world, f, args...; kwargs...)
Call `f(args...; kwargs...)` in a fixed world age, `world`.
This is useful for infrastructure running in the user's Julia session which is
not part of the user's program. For example, things related to the REPL, editor
support libraries, etc. In these cases it can be useful to prevent unwanted
method invalidation and recompilation latency, and to prevent the user from
breaking supporting infrastructure by mistake.
The current world age can be queried using [`Base.get_world_counter()`](@ref)
and stored for later use within the lifetime of the current Julia session, or
when serializing and reloading the system image.
Technically, `invoke_in_world` will prevent any function called by `f` from
being extended by the user during their Julia session. That is, generic
function method tables seen by `f` (and any functions it calls) will be frozen
as they existed at the given `world` age. In a sense, this is like the opposite
of [`invokelatest`](@ref).
!!! note
It is not valid to store world ages obtained in precompilation for later use.
This is because precompilation generates a "parallel universe" where the
world age refers to system state unrelated to the main Julia session.
"""
function invoke_in_world(world::UInt, @nospecialize(f), @nospecialize args...; kwargs...)
if isempty(kwargs)
return Core._apply_in_world(world, f, args)
end
inner() = f(args...; kwargs...)
Core._apply_in_world(world, inner)
end

# TODO: possibly make this an intrinsic
inferencebarrier(@nospecialize(x)) = Ref{Any}(x)[]

Expand Down
1 change: 1 addition & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(sizeof);
DECLARE_BUILTIN(issubtype); DECLARE_BUILTIN(isa);
DECLARE_BUILTIN(_apply); DECLARE_BUILTIN(_apply_pure);
DECLARE_BUILTIN(_apply_latest); DECLARE_BUILTIN(_apply_iterate);
DECLARE_BUILTIN(_apply_in_world);
DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields);
DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec);
DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield);
Expand Down
19 changes: 19 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,24 @@ JL_CALLABLE(jl_f__apply_latest)
return ret;
}

// Like `_apply`, but runs in the specified world.
// If world > jl_world_counter, run in the latest world.
JL_CALLABLE(jl_f__apply_in_world)
{
JL_NARGSV(_apply_in_world, 2);
jl_ptls_t ptls = jl_get_ptls_states();
size_t last_age = ptls->world_age;
JL_TYPECHK(_apply_in_world, ulong, args[0]);
size_t world = jl_unbox_ulong(args[0]);
world = world <= jl_world_counter ? world : jl_world_counter;
if (!ptls->in_pure_callback) {
ptls->world_age = world;
}
jl_value_t *ret = do_apply(NULL, args+1, nargs-1, NULL);
ptls->world_age = last_age;
return ret;
}

// tuples ---------------------------------------------------------------------

JL_CALLABLE(jl_f_tuple)
Expand Down Expand Up @@ -1527,6 +1545,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
jl_builtin_svec = add_builtin_func("svec", jl_f_svec);
add_builtin_func("_apply_pure", jl_f__apply_pure);
add_builtin_func("_apply_latest", jl_f__apply_latest);
add_builtin_func("_apply_in_world", jl_f__apply_in_world);
add_builtin_func("_typevar", jl_f__typevar);
add_builtin_func("_structtype", jl_f__structtype);
add_builtin_func("_abstracttype", jl_f__abstracttype);
Expand Down
1 change: 1 addition & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ static const std::map<jl_fptr_args_t, JuliaFunction*> builtin_func_map = {
{ &jl_f__apply_iterate, new JuliaFunction{"jl_f__apply_iterate", get_func_sig, get_func_attrs} },
{ &jl_f__apply_pure, new JuliaFunction{"jl_f__apply_pure", get_func_sig, get_func_attrs} },
{ &jl_f__apply_latest, new JuliaFunction{"jl_f__apply_latest", get_func_sig, get_func_attrs} },
{ &jl_f__apply_in_world, new JuliaFunction{"jl_f__apply_in_world", get_func_sig, get_func_attrs} },
{ &jl_f_throw, new JuliaFunction{"jl_f_throw", get_func_sig, get_func_attrs} },
{ &jl_f_tuple, jltuple_func },
{ &jl_f_svec, new JuliaFunction{"jl_f_svec", get_func_sig, get_func_attrs} },
Expand Down
3 changes: 2 additions & 1 deletion src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ void *native_functions;
// This is a manually constructed dual of the fvars array, which would be produced by codegen for Julia code, for C.
static const jl_fptr_args_t id_to_fptrs[] = {
&jl_f_throw, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa,
&jl_f_typeassert, &jl_f__apply, &jl_f__apply_iterate, &jl_f__apply_pure, &jl_f__apply_latest, &jl_f_isdefined,
&jl_f_typeassert, &jl_f__apply, &jl_f__apply_iterate, &jl_f__apply_pure,
&jl_f__apply_latest, &jl_f__apply_in_world, &jl_f_isdefined,
&jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call, &jl_f_invoke_kwsorter,
&jl_f_getfield, &jl_f_setfield, &jl_f_fieldtype, &jl_f_nfields,
&jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type,
Expand Down
14 changes: 14 additions & 0 deletions test/worlds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,17 @@ w = worlds(mi)
abstract type Colorant35855 end
Base.convert(::Type{C}, c) where C<:Colorant35855 = false
@test_broken worlds(mi) == w

# invoke_in_world
f_inworld(x) = "world one; x=$x"
g_inworld(x; y) = "world one; x=$x, y=$y"
wc_aiw1 = get_world_counter()
# redefine f_inworld, g_inworld, and check that we can invoke both versions
f_inworld(x) = "world two; x=$x"
g_inworld(x; y) = "world two; x=$x, y=$y"
wc_aiw2 = get_world_counter()
@test Base.invoke_in_world(wc_aiw1, f_inworld, 2) == "world one; x=2"
@test Base.invoke_in_world(wc_aiw2, f_inworld, 2) == "world two; x=2"
@test Base.invoke_in_world(wc_aiw1, g_inworld, 2, y=3) == "world one; x=2, y=3"
@test Base.invoke_in_world(wc_aiw2, g_inworld, 2, y=3) == "world two; x=2, y=3"

0 comments on commit f9496fb

Please sign in to comment.