Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup old builtins #57532

Merged
merged 1 commit into from
Mar 2, 2025
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
2 changes: 1 addition & 1 deletion Compiler/src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState,
# If the method defines a recursion relation, give it a chance
# to tell us that this recursion is actually ok.
if isdefined(method, :recursion_relation)
if Core._apply_pure(method.recursion_relation, Any[method, callee_method2, sig, frame_instance(frame).specTypes])
if Core._call_in_world_total(get_world_counter(), method.recursion_relation, method, callee_method2, sig, frame_instance(frame).specTypes)
return false
end
end
Expand Down
2 changes: 1 addition & 1 deletion Compiler/src/ssair/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function builtin_call_has_dispatch(
return true
end
end
elseif (f === Core._apply_pure || f === Core._call_in_world || f === Core._call_in_world_total || f === Core._call_latest)
elseif (f === Core.invoke_in_world || f === Core._call_in_world_total || f === Core.invokelatest)
# These apply-like builtins are effectively dynamic calls
return true
end
Expand Down
5 changes: 2 additions & 3 deletions Compiler/src/verifytrim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,10 @@ function may_dispatch(@nospecialize ftyp)
# other builtins (including the IntrinsicFunctions) are good
return Core._apply isa ftyp ||
Core._apply_iterate isa ftyp ||
Core._apply_pure isa ftyp ||
Core._call_in_world isa ftyp ||
Core._call_in_world_total isa ftyp ||
Core._call_latest isa ftyp ||
Core.invoke isa ftyp ||
Core.invoke_in_world isa ftyp ||
Core.invokelatest isa ftyp ||
Core.finalizer isa ftyp ||
Core.modifyfield! isa ftyp ||
Core.modifyglobal! isa ftyp ||
Expand Down
57 changes: 57 additions & 0 deletions base/Base_compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,63 @@ function Core._hasmethod(@nospecialize(f), @nospecialize(t)) # this function has
return Core._hasmethod(tt)
end

"""
invokelatest(f, args...; kwargs...)
Calls `f(args...; kwargs...)`, but guarantees that the most recent method of `f`
will be executed. This is useful in specialized circumstances,
e.g. long-running event loops or callback functions that may
call obsolete versions of a function `f`.
(The drawback is that `invokelatest` is somewhat slower than calling
`f` directly, and the type of the result cannot be inferred by the compiler.)
!!! compat "Julia 1.9"
Prior to Julia 1.9, this function was not exported, and was called as `Base.invokelatest`.
"""
const invokelatest = Core.invokelatest

# define invokelatest(f, args...; kwargs...), without kwargs wrapping
# to forward to invokelatest
function Core.kwcall(kwargs::NamedTuple, ::typeof(invokelatest), f, args...)
@inline
return Core.invokelatest(Core.kwcall, kwargs, f, args...)
end
setfield!(typeof(invokelatest).name.mt, :max_args, 2, :monotonic) # invokelatest, f, args...

"""
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.
"""
const invoke_in_world = Core.invoke_in_world

function Core.kwcall(kwargs::NamedTuple, ::typeof(invoke_in_world), world::UInt, f, args...)
@inline
return Core.invoke_in_world(world, Core.kwcall, kwargs, f, args...)
end
setfield!(typeof(invoke_in_world).name.mt, :max_args, 3, :monotonic) # invoke_in_world, world, f, args...

# core operations & types
include("promotion.jl")
include("tuple.jl")
Expand Down
7 changes: 5 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1014,8 +1014,11 @@ _parse = nothing

_setparser!(parser) = setglobal!(Core, :_parse, parser)

# support for deprecated uses of internal _apply function
_apply(x...) = Core._apply_iterate(Main.Base.iterate, x...)
# support for deprecated uses of builtin functions
_apply(x...) = _apply_iterate(Main.Base.iterate, x...)
_apply_pure(x...) = invoke_in_world_total(typemax_UInt, x...)
const _call_latest = invokelatest
const _call_in_world = invoke_in_world

struct Pair{A, B}
first::A
Expand Down
57 changes: 0 additions & 57 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1070,63 +1070,6 @@ end

Val(x) = Val{x}()

"""
invokelatest(f, args...; kwargs...)

Calls `f(args...; kwargs...)`, but guarantees that the most recent method of `f`
will be executed. This is useful in specialized circumstances,
e.g. long-running event loops or callback functions that may
call obsolete versions of a function `f`.
(The drawback is that `invokelatest` is somewhat slower than calling
`f` directly, and the type of the result cannot be inferred by the compiler.)

!!! compat "Julia 1.9"
Prior to Julia 1.9, this function was not exported, and was called as `Base.invokelatest`.
"""
function invokelatest(@nospecialize(f), @nospecialize args...; kwargs...)
@inline
kwargs = merge(NamedTuple(), kwargs)
if isempty(kwargs)
return Core._call_latest(f, args...)
end
return Core._call_latest(Core.kwcall, kwargs, f, args...)
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...)
@inline
kwargs = Base.merge(NamedTuple(), kwargs)
if isempty(kwargs)
return Core._call_in_world(world, f, args...)
end
return Core._call_in_world(world, Core.kwcall, kwargs, f, args...)
end

"""
inferencebarrier(x)

Expand Down
4 changes: 2 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1283,9 +1283,9 @@ function invokelatest_gr(gr::GlobalRef, @nospecialize args...; kwargs...)
@inline
kwargs = merge(NamedTuple(), kwargs)
if isempty(kwargs)
return Core._call_latest(apply_gr, gr, args...)
return invokelatest(apply_gr, gr, args...)
end
return Core._call_latest(apply_gr_kw, kwargs, gr, args...)
return invokelatest(apply_gr_kw, kwargs, gr, args...)
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 @@ -46,7 +46,6 @@ precompile(Tuple{typeof(Base.Threads.atomic_sub!), Base.Threads.Atomic{Int}, Int
precompile(Tuple{Type{Base.Val{x} where x}, Module})
precompile(Tuple{Type{NamedTuple{(:honor_overrides,), T} where T<:Tuple}, Tuple{Bool}})
precompile(Tuple{typeof(Base.unique!), Array{String, 1}})
precompile(Tuple{typeof(Base.invokelatest), Any})
precompile(Tuple{typeof(Base.vcat), Array{String, 1}, Array{String, 1}})
# Pkg loading
Expand Down
13 changes: 2 additions & 11 deletions contrib/juliac-buildscript.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,8 @@ end
init_active_project() = nothing
disable_library_threading() = nothing
start_profile_listener() = nothing
@inline function invokelatest(f::F, args...; kwargs...) where F
return f(args...; kwargs...)
end
@inline function invokelatest_gr(gr::GlobalRef, @nospecialize args...; kwargs...)
@inline
kwargs = merge(NamedTuple(), kwargs)
if isempty(kwargs)
return apply_gr(gr, args...)
end
return apply_gr_kw(kwargs, gr, args...)
end
invokelatest_trimmed(f, args...; kwargs...) = f(args...; kwargs...)
const invokelatest = invokelatest_trimmed
function sprint(f::F, args::Vararg{Any,N}; context=nothing, sizehint::Integer=0) where {F<:Function,N}
s = IOBuffer(sizehint=sizehint)
if context isa Tuple
Expand Down
5 changes: 2 additions & 3 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ extern "C" {
#endif

DECLARE_BUILTIN(_apply_iterate);
DECLARE_BUILTIN(_apply_pure);
DECLARE_BUILTIN(_call_in_world);
DECLARE_BUILTIN(invoke_in_world);
DECLARE_BUILTIN(_call_in_world_total);
DECLARE_BUILTIN(_call_latest);
DECLARE_BUILTIN(invokelatest);
DECLARE_BUILTIN(_compute_sparams);
DECLARE_BUILTIN(_expr);
DECLARE_BUILTIN(_svec_ref);
Expand Down
55 changes: 12 additions & 43 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,14 @@ static jl_value_t *jl_arrayref(jl_array_t *a, size_t i)
return jl_memoryrefget(jl_memoryrefindex(a->ref, i), 0);
}

static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *iterate)
JL_CALLABLE(jl_f__apply_iterate)
{
jl_function_t *f = args[0];
JL_NARGSV(_apply_iterate, 2);
jl_function_t *iterate = args[0];
jl_function_t *f = args[1];
assert(iterate);
args += 1;
nargs -= 1;
if (nargs == 2) {
// some common simple cases
if (f == jl_builtin_svec) {
Expand Down Expand Up @@ -692,9 +697,6 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera
extra += 1;
}
}
if (extra && iterate == NULL) {
jl_undefined_var_error(jl_symbol("iterate"), NULL);
}
// allocate space for the argument array and gc roots for it
// based on our previous estimates
// use the stack if we have a good estimate that it is small
Expand Down Expand Up @@ -841,40 +843,8 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera
return result;
}

JL_CALLABLE(jl_f__apply_iterate)
{
JL_NARGSV(_apply_iterate, 2);
return do_apply(args + 1, nargs - 1, args[0]);
}

// this is like `_apply`, but with quasi-exact checks to make sure it is pure
JL_CALLABLE(jl_f__apply_pure)
{
jl_task_t *ct = jl_current_task;
int last_in = ct->ptls->in_pure_callback;
jl_value_t *ret = NULL;
JL_TRY {
ct->ptls->in_pure_callback = 1;
// because this function was declared pure,
// we should be allowed to run it in any world
// so we run it in the newest world;
// because, why not :)
// and `promote` works better this way
size_t last_age = ct->world_age;
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
ret = do_apply(args, nargs, NULL);
ct->world_age = last_age;
ct->ptls->in_pure_callback = last_in;
}
JL_CATCH {
ct->ptls->in_pure_callback = last_in;
jl_rethrow();
}
return ret;
}

// this is like a regular call, but always runs in the newest world
JL_CALLABLE(jl_f__call_latest)
JL_CALLABLE(jl_f_invokelatest)
{
jl_task_t *ct = jl_current_task;
size_t last_age = ct->world_age;
Expand All @@ -885,9 +855,9 @@ JL_CALLABLE(jl_f__call_latest)
return ret;
}

// Like call_in_world, but runs in the specified world.
// Like invokelatest, but runs in the specified world.
// If world > jl_atomic_load_acquire(&jl_world_counter), run in the latest world.
JL_CALLABLE(jl_f__call_in_world)
JL_CALLABLE(jl_f_invoke_in_world)
{
JL_NARGSV(_apply_in_world, 2);
jl_task_t *ct = jl_current_task;
Expand Down Expand Up @@ -2539,9 +2509,8 @@ void jl_init_primitives(void) JL_GC_DISABLED
jl_builtin__apply_iterate = add_builtin_func("_apply_iterate", jl_f__apply_iterate);
jl_builtin__expr = add_builtin_func("_expr", jl_f__expr);
jl_builtin_svec = add_builtin_func("svec", jl_f_svec);
add_builtin_func("_apply_pure", jl_f__apply_pure);
add_builtin_func("_call_latest", jl_f__call_latest);
add_builtin_func("_call_in_world", jl_f__call_in_world);
add_builtin_func("invokelatest", jl_f_invokelatest);
add_builtin_func("invoke_in_world", jl_f_invoke_in_world);
add_builtin_func("_call_in_world_total", jl_f__call_in_world_total);
add_builtin_func("_typevar", jl_f__typevar);
add_builtin_func("_structtype", jl_f__structtype);
Expand Down
5 changes: 2 additions & 3 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1570,9 +1570,8 @@ static const auto &builtin_func_map() {
{ jl_f_typeassert_addr, new JuliaFunction<>{XSTR(jl_f_typeassert), get_func_sig, get_func_attrs} },
{ jl_f_ifelse_addr, new JuliaFunction<>{XSTR(jl_f_ifelse), get_func_sig, get_func_attrs} },
{ jl_f__apply_iterate_addr, new JuliaFunction<>{XSTR(jl_f__apply_iterate), get_func_sig, get_func_attrs} },
{ jl_f__apply_pure_addr, new JuliaFunction<>{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} },
{ jl_f__call_latest_addr, new JuliaFunction<>{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} },
{ jl_f__call_in_world_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} },
{ jl_f_invokelatest_addr, new JuliaFunction<>{XSTR(jl_f_invokelatest), get_func_sig, get_func_attrs} },
{ jl_f_invoke_in_world_addr, new JuliaFunction<>{XSTR(jl_f_invoke_in_world), get_func_sig, get_func_attrs} },
{ jl_f__call_in_world_total_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} },
{ jl_f_throw_addr, new JuliaFunction<>{XSTR(jl_f_throw), get_func_sig, get_func_attrs} },
{ jl_f_throw_methoderror_addr, new JuliaFunction<>{XSTR(jl_f_throw_methoderror), get_func_sig, get_func_attrs} },
Expand Down
4 changes: 2 additions & 2 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,8 @@ static htable_t bits_replace;
// 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_throw_methoderror, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa,
&jl_f_typeassert, &jl_f__apply_iterate, &jl_f__apply_pure,
&jl_f__call_latest, &jl_f__call_in_world, &jl_f__call_in_world_total, &jl_f_isdefined, &jl_f_isdefinedglobal,
&jl_f_typeassert, &jl_f__apply_iterate,
&jl_f_invokelatest, &jl_f_invoke_in_world, &jl_f__call_in_world_total, &jl_f_isdefined, &jl_f_isdefinedglobal,
&jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call,
&jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, &jl_f_setfieldonce,
&jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, &jl_f_apply_type, &jl_f_memorynew,
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/test/precompilation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ if !Sys.iswindows()
# given this test checks that startup is snappy, it's best to add workloads to
# contrib/generate_precompile.jl rather than increase this number. But if that's not
# possible, it'd be helpful to add a comment with the statement and a reason below
expected_precompiles = 1
expected_precompiles = 0

n_precompiles = count(r"precompile\(", tracecompile_out)

Expand Down
5 changes: 2 additions & 3 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,8 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri
@test occursin("shell> ", s) # check for the echo of the prompt
@test occursin("'", s) # check for the echo of the input
s = readuntil(stdout_read, "\n\n")
@test(startswith(s, "\e[0mERROR: unterminated single quote\nStacktrace:\n [1] ") ||
startswith(s, "\e[0m\e[1m\e[91mERROR: \e[39m\e[22m\e[91munterminated single quote\e[39m\nStacktrace:\n [1] "),
skip = Sys.iswindows() && Sys.WORD_SIZE == 32)
@test(startswith(s, "\e[0mERROR: unterminated single quote\nStacktrace:\n [1] ") ||
startswith(s, "\e[0m\e[1m\e[91mERROR: \e[39m\e[22m\e[91munterminated single quote\e[39m\nStacktrace:\n [1] "))
write(stdin_write, "\b")
wait(t)
end
Expand Down