diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 330551103ab8dd..9a9fa9eb8e0376 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -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 diff --git a/Compiler/src/ssair/show.jl b/Compiler/src/ssair/show.jl index e63d7b5cf640ed..0688c02eb64406 100644 --- a/Compiler/src/ssair/show.jl +++ b/Compiler/src/ssair/show.jl @@ -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 diff --git a/Compiler/src/verifytrim.jl b/Compiler/src/verifytrim.jl index 7706d89483cbf8..67728a15177b43 100644 --- a/Compiler/src/verifytrim.jl +++ b/Compiler/src/verifytrim.jl @@ -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 || diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index a1ae14427e7546..fc2304f8fce461 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -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") diff --git a/base/boot.jl b/base/boot.jl index 0d59156b246451..e7679f7226d5d8 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -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 diff --git a/base/essentials.jl b/base/essentials.jl index 66c79843a1453f..5e983abb1e61dc 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -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) diff --git a/base/reflection.jl b/base/reflection.jl index c98f6244cc89f7..50b4413f01b599 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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 """ diff --git a/contrib/juliac-buildscript.jl b/contrib/juliac-buildscript.jl index 7d25e85ff4c5c6..459922ab1ccb1e 100644 --- a/contrib/juliac-buildscript.jl +++ b/contrib/juliac-buildscript.jl @@ -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 diff --git a/src/builtin_proto.h b/src/builtin_proto.h index a543aa895fb977..c82ec77414129c 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -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); diff --git a/src/builtins.c b/src/builtins.c index 063b191510bfd3..243d14f34d3bd9 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -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) { @@ -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 @@ -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; @@ -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; @@ -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); diff --git a/src/codegen.cpp b/src/codegen.cpp index 36644e1e34491b..194d5e023fc481 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -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} }, diff --git a/src/staticdata.c b/src/staticdata.c index 0025e029a98d9d..f0827c9d4a793b 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -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, diff --git a/stdlib/REPL/test/precompilation.jl b/stdlib/REPL/test/precompilation.jl index 01a062644596c7..7efcf0b5e82827 100644 --- a/stdlib/REPL/test/precompilation.jl +++ b/stdlib/REPL/test/precompilation.jl @@ -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) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index c1c5c7844bc963..e4b291a37fec1e 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -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