Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"