diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 300feeae53e9a..2b1a7fb2dd448 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3456,10 +3456,10 @@ world_range(ir::IRCode) = ir.valid_worlds world_range(ci::CodeInfo) = WorldRange(ci.min_world, ci.max_world) world_range(compact::IncrementalCompact) = world_range(compact.ir) -function force_binding_resolution!(g::GlobalRef) +function force_binding_resolution!(g::GlobalRef, world::UInt) # Force resolution of the binding # TODO: This will go away once we switch over to fully partitioned semantics - ccall(:jl_globalref_boundp, Cint, (Any,), g) + ccall(:jl_force_binding_resolution, Cvoid, (Any, Csize_t), g, world) return nothing end @@ -3477,7 +3477,7 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, # This method is surprisingly hot. For performance, don't ask the runtime to resolve # the binding unless necessary - doing so triggers an additional lookup, which though # not super expensive is hot enough to show up in benchmarks. - force_binding_resolution!(g) + force_binding_resolution!(g, min_world(worlds)) return abstract_eval_globalref_type(g, src, false) end # return Union{} @@ -3490,7 +3490,7 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, end function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - force_binding_resolution!(g) + force_binding_resolution!(g, get_inference_world(interp)) partition = lookup_binding_partition(get_inference_world(interp), g) update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world)) partition @@ -3539,7 +3539,8 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, saw_ partition = abstract_eval_binding_partition!(interp, g, sv) ret = abstract_eval_partition_load(interp, partition) if ret.rt !== Union{} && ret.exct === UndefVarError && InferenceParams(interp).assume_bindings_static - if isdefined(g, :binding) && isdefined(g.binding, :value) + b = convert(Core.Binding, g) + if isdefined(b, :value) ret = RTEffects(ret.rt, Union{}, Effects(generic_getglobal_effects, nothrow=true)) end # We do not assume in general that assigned global bindings remain assigned. diff --git a/Compiler/src/optimize.jl b/Compiler/src/optimize.jl index 12b2f3c9a269f..9f74f028507cd 100644 --- a/Compiler/src/optimize.jl +++ b/Compiler/src/optimize.jl @@ -1286,7 +1286,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # types of call arguments only once `slot2reg` converts this `IRCode` to the SSA form # and eliminates slots (see below) argtypes = sv.slottypes - return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes, WorldRange(ci.min_world, ci.max_world)) + return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes, world_range(ci)) end function process_meta!(meta::Vector{Expr}, @nospecialize stmt) diff --git a/Compiler/src/ssair/ir.jl b/Compiler/src/ssair/ir.jl index 9103dba04fa54..f86ada2309ddc 100644 --- a/Compiler/src/ssair/ir.jl +++ b/Compiler/src/ssair/ir.jl @@ -434,7 +434,7 @@ struct IRCode function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}, valid_worlds=WorldRange(typemin(UInt), typemax(UInt))) - return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta) + return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta, valid_worlds) end function IRCode(ir::IRCode, stmts::InstructionStream, cfg::CFG, new_nodes::NewNodeStream) di = ir.debuginfo @@ -1462,7 +1462,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr result[result_idx][:stmt] = GotoNode(label) result_idx += 1 elseif isa(stmt, GlobalRef) - total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE + total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW flag = result[result_idx][:flag] if has_flag(flag, total_flags) ssa_rename[idx] = stmt diff --git a/Compiler/src/ssair/legacy.jl b/Compiler/src/ssair/legacy.jl index 675ca2dea9b32..64a39c72ad9eb 100644 --- a/Compiler/src/ssair/legacy.jl +++ b/Compiler/src/ssair/legacy.jl @@ -44,7 +44,7 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A di = DebugInfoStream(nothing, ci.debuginfo, nstmts) stmts = InstructionStream(code, ssavaluetypes, info, di.codelocs, ci.ssaflags) meta = Expr[] - return IRCode(stmts, cfg, di, argtypes, meta, sptypes, WorldRange(ci.min_world, ci.max_world)) + return IRCode(stmts, cfg, di, argtypes, meta, sptypes, world_range(ci)) end """ diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index 12eb09be693f3..fa16bdcc7ab19 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -12,7 +12,7 @@ end if !isdefined(@__MODULE__, Symbol("@verify_error")) macro verify_error(arg) - arg isa String && return esc(:(print && println(stderr, $arg))) + arg isa String && return esc(:(print && println($(GlobalRef(Core, :stderr)), $arg))) isexpr(arg, :string) || error("verify_error macro expected a string expression") pushfirst!(arg.args, GlobalRef(Core, :stderr)) pushfirst!(arg.args, :println) @@ -61,8 +61,14 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, raise_error() end elseif isa(op, GlobalRef) - if !isdefined(op.mod, op.name) || !isconst(op.mod, op.name) - @verify_error "Unbound GlobalRef not allowed in value position" + force_binding_resolution!(op, min_world(ir.valid_worlds)) + bpart = lookup_binding_partition(min_world(ir.valid_worlds), op) + while is_some_imported(binding_kind(bpart)) && max_world(ir.valid_worlds) <= bpart.max_world + imported_binding = partition_restriction(bpart)::Core.Binding + bpart = lookup_binding_partition(min_world(ir.valid_worlds), imported_binding) + end + if !is_defined_const_binding(binding_kind(bpart)) || (bpart.max_world < max_world(ir.valid_worlds)) + @verify_error "Unbound or partitioned GlobalRef not allowed in value position" raise_error() end elseif isa(op, Expr) diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index fe21f6381e8cd..74c8026ca0cf5 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -1109,7 +1109,7 @@ function _getfield_tfunc_const(@nospecialize(sv), name::Const) if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv) return Const(getfield(sv, nv)) end - if isconst(typeof(sv), nv) + if !isa(sv, Module) && isconst(typeof(sv), nv) if isdefined(sv, nv) return Const(getfield(sv, nv)) end diff --git a/Compiler/test/abioverride.jl b/Compiler/test/abioverride.jl index da9b1f92786e5..49907ea8e4c63 100644 --- a/Compiler/test/abioverride.jl +++ b/Compiler/test/abioverride.jl @@ -40,6 +40,7 @@ let world = Base.tls_world_age() ## Remove the argument resize!(new_source.slotnames, 2) resize!(new_source.slotflags, 2) + new_source.nargs = 2 # Construct the CodeInstance from the modified CodeInfo data global new_ci = Core.CodeInstance(Core.ABIOverride(Tuple{typeof(myplus), Int}, mi), diff --git a/Compiler/test/codegen.jl b/Compiler/test/codegen.jl index ff61ac719c59e..57a9c26aefac6 100644 --- a/Compiler/test/codegen.jl +++ b/Compiler/test/codegen.jl @@ -1041,3 +1041,20 @@ struct Vec56937 x::NTuple{8, VecElement{Int}} end x56937 = Ref(Vec56937(ntuple(_->VecElement(1),8))) @test x56937[].x[1] == VecElement{Int}(1) # shouldn't crash + +# issue #56996 +let + ()->() # trigger various heuristics + Base.Experimental.@force_compile + default_rng_orig = [] # make a value in a Slot + try + # overwrite the gc-slots in the exception branch + throw(ErrorException("This test is supposed to throw an error")) + catch ex + # destroy any values that aren't referenced + GC.gc() + # make sure that default_rng_orig value is still valid + @noinline copy!([], default_rng_orig) + end + nothing +end diff --git a/Compiler/test/inline.jl b/Compiler/test/inline.jl index 5f95fb761859e..b8ff14405391d 100644 --- a/Compiler/test/inline.jl +++ b/Compiler/test/inline.jl @@ -149,7 +149,7 @@ end (src, _) = only(code_typed(sum27403, Tuple{Vector{Int}})) @test !any(src.code) do x - x isa Expr && x.head === :invoke && x.args[2] !== Core.GlobalRef(Base, :throw_boundserror) + x isa Expr && x.head === :invoke && !(x.args[2] in (Core.GlobalRef(Base, :throw_boundserror), Base.throw_boundserror)) end end @@ -313,7 +313,7 @@ end const _a_global_array = [1] f_inline_global_getindex() = _a_global_array[1] let ci = code_typed(f_inline_global_getindex, Tuple{})[1].first - @test any(x->(isexpr(x, :call) && x.args[1] === GlobalRef(Base, :memoryrefget)), ci.code) + @test any(x->(isexpr(x, :call) && x.args[1] in (GlobalRef(Base, :memoryrefget), Base.memoryrefget)), ci.code) end # Issue #29114 & #36087 - Inlining of non-tuple splats diff --git a/base/Base.jl b/base/Base.jl index 20b1636c29a8d..04f732a4309c9 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -401,6 +401,7 @@ end # we know whether the .ji can just give the Base copy or not. # TODO: We may want to do this earlier to avoid TOCTOU issues. const _compiler_require_dependencies = Any[] +@Core.latestworld for i = 1:length(_included_files) isassigned(_included_files, i) || continue (mod, file) = _included_files[i] diff --git a/base/client.jl b/base/client.jl index e95d518d3e501..2527d382c695d 100644 --- a/base/client.jl +++ b/base/client.jl @@ -299,7 +299,7 @@ function exec_options(opts) elseif cmd == 'm' entrypoint = push!(split(arg, "."), "main") Base.eval(Main, Expr(:import, Expr(:., Symbol.(entrypoint)...))) - if !should_use_main_entrypoint() + if !invokelatest(should_use_main_entrypoint) error("`main` in `$arg` not declared as entry point (use `@main` to do so)") end return false @@ -408,8 +408,7 @@ function load_InteractiveUtils(mod::Module=Main) return nothing end end - Core.eval(mod, :(using Base.MainInclude.InteractiveUtils)) - return MainInclude.InteractiveUtils + return Core.eval(mod, :(using Base.MainInclude.InteractiveUtils; Base.MainInclude.InteractiveUtils)) end function load_REPL() @@ -556,11 +555,12 @@ function _start() local ret = 0 try repl_was_requested = exec_options(JLOptions()) - if should_use_main_entrypoint() && !is_interactive + if invokelatest(should_use_main_entrypoint) && !is_interactive + main = invokelatest(getglobal, Main, :main) if Base.generating_output() - precompile(Main.main, (typeof(ARGS),)) + precompile(main, (typeof(ARGS),)) else - ret = invokelatest(Main.main, ARGS) + ret = invokelatest(main, ARGS) end elseif (repl_was_requested || is_interactive) # Run the Base `main`, which will either load the REPL stdlib diff --git a/base/essentials.jl b/base/essentials.jl index fa5cf79192f56..58e4ce1125093 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -1266,6 +1266,9 @@ arbitrary code in fixed worlds. `world` may be `UnitRange`, in which case the ma will error unless the binding is valid and has the same value across the entire world range. +As a special case, the world `∞` always refers to the latest world, even if that world +is newer than the world currently running. + The `@world` macro is primarily used in the printing of bindings that are no longer available in the current world. @@ -1290,6 +1293,9 @@ julia> fold This functionality requires at least Julia 1.12. """ macro world(sym, world) + if world == :∞ + world = Expr(:call, get_world_counter) + end if isa(sym, Symbol) return :($(_resolve_in_world)($(esc(world)), $(QuoteNode(GlobalRef(__module__, sym))))) elseif isa(sym, GlobalRef) diff --git a/base/gcutils.jl b/base/gcutils.jl index 84a184537ffc0..60b8ecdd17d65 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -243,12 +243,21 @@ end GC.safepoint() Inserts a point in the program where garbage collection may run. -This can be useful in rare cases in multi-threaded programs where some threads -are allocating memory (and hence may need to run GC) but other threads are doing -only simple operations (no allocation, task switches, or I/O). -Calling this function periodically in non-allocating threads allows garbage + +Safepoints are fast and do not themselves trigger garbage collection. +However, if another thread has requested the GC to run, reaching a safepoint will +cause the current thread to block and wait for the GC. + +This can be useful in rare cases in multi-threaded programs where some tasks +are allocating memory (and hence may need to run GC) but other tasks are doing +only simple operations (no allocation, task switches, or I/O), which do not +yield control to Julia's runtime, and therefore blocks the GC from running. +Calling this function periodically in the non-allocating tasks allows garbage collection to run. +Note that even though safepoints are fast (typically around 2 clock cycles), +they can still degrade performance if called in a tight loop. + !!! compat "Julia 1.4" This function is available as of Julia 1.4. """ diff --git a/base/loading.jl b/base/loading.jl index 4193aae13b96a..240406292246b 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2364,6 +2364,18 @@ function require(into::Module, mod::Symbol) return invoke_in_world(world, __require, into, mod) end +function check_for_hint(into, mod) + return begin + if isdefined(into, mod) && getfield(into, mod) isa Module + true, "." + elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module + true, ".." + else + false, "" + end + end +end + function __require(into::Module, mod::Symbol) if into === __toplevel__ && generating_output(#=incremental=#true) error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \ @@ -2377,15 +2389,7 @@ function __require(into::Module, mod::Symbol) if uuidkey_env === nothing where = PkgId(into) if where.uuid === nothing - hint, dots = begin - if isdefined(into, mod) && getfield(into, mod) isa Module - true, "." - elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module - true, ".." - else - false, "" - end - end + hint, dots = invokelatest(check_for_hint, into, mod) hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : "" install_message = if mod != :Pkg start_sentence = hint ? "Otherwise, run" : "Run" diff --git a/base/logging/logging.jl b/base/logging/logging.jl index 41721e84cc934..a1a8417bcb388 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -372,7 +372,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true else - @invokelatest logging_error(logger, level, _module, group, id, file, line, err, false) + @invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, false) false end end @@ -384,7 +384,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true catch err - @invokelatest logging_error(logger, level, _module, group, id, file, line, err, true) + @invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, true) false end end diff --git a/base/reflection.jl b/base/reflection.jl index f9c5dd9765533..78e701692a2a7 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1362,6 +1362,18 @@ macro invoke(ex) return esc(out) end +apply_gr(gr::GlobalRef, @nospecialize args...) = getglobal(gr.mod, gr.name)(args...) +apply_gr_kw(@nospecialize(kwargs::NamedTuple), gr::GlobalRef, @nospecialize args...) = Core.kwcall(kwargs, getglobal(gr.mod, gr.name), args...) + +function invokelatest_gr(gr::GlobalRef, @nospecialize args...; kwargs...) + @inline + kwargs = merge(NamedTuple(), kwargs) + if isempty(kwargs) + return Core._call_latest(apply_gr, gr, args...) + end + return Core._call_latest(apply_gr_kw, kwargs, gr, args...) +end + """ @invokelatest f(args...; kwargs...) @@ -1375,22 +1387,11 @@ It also supports the following syntax: - `@invokelatest xs[i]` expands to `Base.invokelatest(getindex, xs, i)` - `@invokelatest xs[i] = v` expands to `Base.invokelatest(setindex!, xs, v, i)` -```jldoctest -julia> @macroexpand @invokelatest f(x; kw=kwv) -:(Base.invokelatest(f, x; kw = kwv)) - -julia> @macroexpand @invokelatest x.f -:(Base.invokelatest(Base.getproperty, x, :f)) - -julia> @macroexpand @invokelatest x.f = v -:(Base.invokelatest(Base.setproperty!, x, :f, v)) - -julia> @macroexpand @invokelatest xs[i] -:(Base.invokelatest(Base.getindex, xs, i)) - -julia> @macroexpand @invokelatest xs[i] = v -:(Base.invokelatest(Base.setindex!, xs, v, i)) -``` +!!! note + If `f` is a global, it will be resolved consistently + in the (latest) world as the call target. However, all other arguments + (as well as `f` itself if it is not a literal global) will be evaluated + in the current world age. !!! compat "Julia 1.7" This macro requires Julia 1.7 or later. @@ -1404,11 +1405,45 @@ julia> @macroexpand @invokelatest xs[i] = v macro invokelatest(ex) topmod = _topmod(__module__) f, args, kwargs = destructure_callex(topmod, ex) - out = Expr(:call, GlobalRef(Base, :invokelatest)) - isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) - push!(out.args, f) - append!(out.args, args) - return esc(out) + + if !isa(f, GlobalRef) + out_f = Expr(:call, GlobalRef(Base, :invokelatest)) + isempty(kwargs) || push!(out_f.args, Expr(:parameters, kwargs...)) + + if isexpr(f, :(.)) + s = gensym() + check = quote + $s = $(f.args[1]) + isa($s, Module) + end + push!(out_f.args, Expr(:(.), s, f.args[2])) + else + push!(out_f.args, f) + end + append!(out_f.args, args) + + if @isdefined(s) + f = :(GlobalRef($s, $(f.args[2]))) + elseif !isa(f, Symbol) + return esc(out_f) + else + check = :($(Expr(:isglobal, f))) + end + end + + out_gr = Expr(:call, GlobalRef(Base, :invokelatest_gr)) + isempty(kwargs) || push!(out_gr.args, Expr(:parameters, kwargs...)) + push!(out_gr.args, isa(f, GlobalRef) ? QuoteNode(f) : + isa(f, Symbol) ? QuoteNode(GlobalRef(__module__, f)) : + f) + append!(out_gr.args, args) + + if isa(f, GlobalRef) + return esc(out_gr) + end + + # f::Symbol + return esc(:($check ? $out_gr : $out_f)) end function destructure_callex(topmod::Module, @nospecialize(ex)) diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 67694e533ac47..964e8063dd5af 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -239,12 +239,16 @@ function lookup_binding_partition(world::UInt, b::Core.Binding) ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world) end -function lookup_binding_partition(world::UInt, gr::Core.GlobalRef) +function convert(::Type{Core.Binding}, gr::Core.GlobalRef) if isdefined(gr, :binding) - b = gr.binding + return gr.binding else - b = ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), gr.mod, gr.name, true) + return ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), gr.mod, gr.name, true) end +end + +function lookup_binding_partition(world::UInt, gr::Core.GlobalRef) + b = convert(Core.Binding, gr) return lookup_binding_partition(world, b) end @@ -420,7 +424,11 @@ end """ isconst(t::DataType, s::Union{Int,Symbol}) -> Bool -Determine whether a field `s` is declared `const` in a given type `t`. +Determine whether a field `s` is const in a given type `t` +in the sense that a read from said field is consistent +for egal objects. Note in particular that out-of-bounds +fields are considered const under this definition (because +they always throw). """ function isconst(@nospecialize(t::Type), s::Symbol) @_foldable_meta diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index 5c8315693c71e..24e64b0ca068e 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -63,7 +63,7 @@ Each kind of environment defines these three maps differently, as detailed in th ### Project environments -A project environment is determined by a directory containing a project file called `Project.toml`, and optionally a manifest file called `Manifest.toml`. These files may also be called `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored. This allows for coexistence with other tools that might consider files called `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` are preferred. However, from Julia v1.11 onwards, `(Julia)Manifest-v{major}.{minor}.toml` is recognized as a format to make a given julia version use a specific manifest file i.e. in the same folder, a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by any other julia version. +A project environment is determined by a directory containing a project file called `Project.toml`, and optionally a manifest file called `Manifest.toml`. These files may also be called `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored. This allows for coexistence with other tools that might consider files called `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` are preferred. However, from Julia v1.10.8 onwards, `(Julia)Manifest-v{major}.{minor}.toml` is recognized as a format to make a given julia version use a specific manifest file i.e. in the same folder, a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by any other julia version. The roots, graph and paths maps of a project environment are defined as follows: diff --git a/src/codegen.cpp b/src/codegen.cpp index 7cfcc52db29c7..60e53f75638ed 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3136,8 +3136,11 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) { if (jl_is_symbol(ex)) { jl_sym_t *sym = (jl_sym_t*)ex; - if (jl_is_const(ctx.module, sym)) - return jl_get_global(ctx.module, sym); + jl_binding_t *bnd = jl_get_module_binding(ctx.module, sym, 0); + jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return decode_restriction_value(pku); return NULL; } if (jl_is_slotnumber(ex) || jl_is_argument(ex)) @@ -3158,11 +3161,15 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_sym_t *s = NULL; if (jl_is_globalref(ex)) { s = jl_globalref_name(ex); - jl_binding_t *b = jl_get_binding(jl_globalref_mod(ex), s); - jl_value_t *v = jl_get_binding_value_if_const(b); + jl_binding_t *bnd = jl_get_module_binding(jl_globalref_mod(ex), s, 0); + jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + jl_value_t *v = NULL; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + v = decode_restriction_value(pku); if (v) { - if (b->deprecated) - cg_bdw(ctx, s, b); + if (bnd->deprecated) + cg_bdw(ctx, s, bnd); return v; } return NULL; @@ -3181,11 +3188,15 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) // Assumes that the module is rooted somewhere. s = (jl_sym_t*)static_eval(ctx, jl_exprarg(e, 2)); if (s && jl_is_symbol(s)) { - jl_binding_t *b = jl_get_binding(m, s); - jl_value_t *v = jl_get_binding_value_if_const(b); + jl_binding_t *bnd = jl_get_module_binding(m, s, 0); + jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + jl_value_t *v = NULL; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + v = decode_restriction_value(pku); if (v) { - if (b->deprecated) - cg_bdw(ctx, s, b); + if (bnd->deprecated) + cg_bdw(ctx, s, bnd); return v; } } @@ -3271,24 +3282,7 @@ static bool local_var_occurs(jl_value_t *e, int sl) return false; } -static std::set assigned_in_try(jl_array_t *stmts, int s, long l) -{ - std::set av; - for(int i=s; i < l; i++) { - jl_value_t *st = jl_array_ptr_ref(stmts,i); - if (jl_is_expr(st)) { - if (((jl_expr_t*)st)->head == jl_assign_sym) { - jl_value_t *ar = jl_exprarg(st, 0); - if (jl_is_slotnumber(ar)) { - av.insert(jl_slot_number(ar)-1); - } - } - } - } - return av; -} - -static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl &slots) +static bool have_try_block(jl_array_t *stmts) { size_t slength = jl_array_dim0(stmts); for (int i = 0; i < (int)slength; i++) { @@ -3297,19 +3291,38 @@ static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl int last = jl_enternode_catch_dest(st); if (last == 0) continue; - std::set as = assigned_in_try(stmts, i + 1, last - 1); - for (int j = 0; j < (int)slength; j++) { - if (j < i || j > last) { - std::set::iterator it = as.begin(); - for (; it != as.end(); it++) { - if (local_var_occurs(jl_array_ptr_ref(stmts, j), *it)) { - jl_varinfo_t &vi = slots[*it]; - vi.isVolatile = true; - } - } + return 1; + } + } + return 0; +} + +// conservative marking of all variables potentially used after a catch block that were assigned before it +static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl &slots, const std::set &bbstarts) +{ + if (!have_try_block(stmts)) + return; + size_t slength = jl_array_dim0(stmts); + BitVector assigned_in_block(slots.size()); // conservatively only ignore slots assigned in the same basic block + for (int j = 0; j < (int)slength; j++) { + if (bbstarts.count(j + 1)) + assigned_in_block.reset(); + jl_value_t *stmt = jl_array_ptr_ref(stmts, j); + if (jl_is_expr(stmt)) { + jl_expr_t *e = (jl_expr_t*)stmt; + if (e->head == jl_assign_sym) { + jl_value_t *l = jl_exprarg(e, 0); + if (jl_is_slotnumber(l)) { + assigned_in_block.set(jl_slot_number(l)-1); } } } + for (int slot = 0; slot < (int)slots.size(); slot++) { + if (!assigned_in_block.test(slot) && local_var_occurs(stmt, slot)) { + jl_varinfo_t &vi = slots[slot]; + vi.isVolatile = true; + } + } } } @@ -3465,14 +3478,13 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return mark_julia_const(ctx, constval); } } - if (!bpart) { + if (!bpart || decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { return emit_globalref_runtime(ctx, bnd, mod, name); } Value *bp = julia_binding_gv(ctx, bnd); if (bnd->deprecated) { cg_bdw(ctx, name, bnd); } - assert(decode_restriction_kind(pku) == BINDING_KIND_GLOBAL); jl_value_t *ty = decode_restriction_value(pku); bp = julia_binding_pvalue(ctx, bp); if (ty == nullptr) @@ -8429,7 +8441,6 @@ static jl_llvm_functions_t ctx.code = src->code; ctx.source = src; - std::map labels; ctx.module = jl_is_method(lam->def.method) ? lam->def.method->module : lam->def.module; ctx.linfo = lam; ctx.name = name_from_method_instance(lam); @@ -8489,6 +8500,49 @@ static jl_llvm_functions_t if (dbgFuncName.empty()) // Should never happen anymore? debug_enabled = false; + // First go through and collect all branch targets, so we know where to + // split basic blocks. + std::set branch_targets; // 1-indexed, sorted + for (size_t i = 0; i < stmtslen; ++i) { + jl_value_t *stmt = jl_array_ptr_ref(stmts, i); + if (jl_is_gotoifnot(stmt)) { + int dest = jl_gotoifnot_label(stmt); + branch_targets.insert(dest); + // The next 1-indexed statement + branch_targets.insert(i + 2); + } + else if (jl_is_returnnode(stmt)) { + // We don't do dead branch elimination before codegen + // so we need to make sure to start a BB after any + // return node, even if they aren't otherwise branch + // targets. + if (i + 2 <= stmtslen) + branch_targets.insert(i + 2); + } + else if (jl_is_enternode(stmt)) { + branch_targets.insert(i + 1); + if (i + 2 <= stmtslen) + branch_targets.insert(i + 2); + size_t catch_dest = jl_enternode_catch_dest(stmt); + if (catch_dest) + branch_targets.insert(catch_dest); + } + else if (jl_is_gotonode(stmt)) { + int dest = jl_gotonode_label(stmt); + branch_targets.insert(dest); + if (i + 2 <= stmtslen) + branch_targets.insert(i + 2); + } + else if (jl_is_phinode(stmt)) { + jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(stmt, 0); + for (size_t j = 0; j < jl_array_nrows(edges); ++j) { + size_t edge = jl_array_data(edges, int32_t)[j]; + if (edge == i) + branch_targets.insert(i + 1); + } + } + } + // step 2. process var-info lists to see what vars need boxing int n_ssavalues = jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_nrows(src->ssavaluetypes); size_t vinfoslen = jl_array_dim0(src->slotflags); @@ -8549,7 +8603,7 @@ static jl_llvm_functions_t simple_use_analysis(ctx, jl_array_ptr_ref(stmts, i)); // determine which vars need to be volatile - mark_volatile_vars(stmts, ctx.slots); + mark_volatile_vars(stmts, ctx.slots, branch_targets); // step 4. determine function signature if (!specsig) @@ -9300,8 +9354,8 @@ static jl_llvm_functions_t // step 11b. Do codegen in control flow order SmallVector workstack; - std::map BB; - std::map come_from_bb; + DenseMap BB; + DenseMap come_from_bb; int cursor = 0; int current_label = 0; auto find_next_stmt = [&] (int seq_next) { @@ -9420,47 +9474,6 @@ static jl_llvm_functions_t come_from_bb[0] = ctx.builder.GetInsertBlock(); - // First go through and collect all branch targets, so we know where to - // split basic blocks. - std::set branch_targets; // 1-indexed - { - for (size_t i = 0; i < stmtslen; ++i) { - jl_value_t *stmt = jl_array_ptr_ref(stmts, i); - if (jl_is_gotoifnot(stmt)) { - int dest = jl_gotoifnot_label(stmt); - branch_targets.insert(dest); - // The next 1-indexed statement - branch_targets.insert(i + 2); - } else if (jl_is_returnnode(stmt)) { - // We don't do dead branch elimination before codegen - // so we need to make sure to start a BB after any - // return node, even if they aren't otherwise branch - // targets. - if (i + 2 <= stmtslen) - branch_targets.insert(i + 2); - } else if (jl_is_enternode(stmt)) { - branch_targets.insert(i + 1); - if (i + 2 <= stmtslen) - branch_targets.insert(i + 2); - size_t catch_dest = jl_enternode_catch_dest(stmt); - if (catch_dest) - branch_targets.insert(catch_dest); - } else if (jl_is_gotonode(stmt)) { - int dest = jl_gotonode_label(stmt); - branch_targets.insert(dest); - if (i + 2 <= stmtslen) - branch_targets.insert(i + 2); - } else if (jl_is_phinode(stmt)) { - jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(stmt, 0); - for (size_t j = 0; j < jl_array_nrows(edges); ++j) { - size_t edge = jl_array_data(edges, int32_t)[j]; - if (edge == i) - branch_targets.insert(i + 1); - } - } - } - } - for (int label : branch_targets) { BasicBlock *bb = BasicBlock::Create(ctx.builder.getContext(), "L" + std::to_string(label), f); diff --git a/src/gf.c b/src/gf.c index ea25ed2eae4ff..710dda208f0b2 100644 --- a/src/gf.c +++ b/src/gf.c @@ -292,7 +292,8 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a { jl_sym_t *sname = jl_symbol(name); if (dt == NULL) { - jl_value_t *f = jl_new_generic_function_with_supertype(sname, jl_core_module, jl_builtin_type); + // Builtins are specially considered available from world 0 + jl_value_t *f = jl_new_generic_function_with_supertype(sname, jl_core_module, jl_builtin_type, 0); jl_set_const(jl_core_module, sname, f); dt = (jl_datatype_t*)jl_typeof(f); } @@ -3790,10 +3791,8 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value return _jl_invoke(gf, args, nargs - 1, mfunc, world); } -// Return value is rooted globally -jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st) +jl_sym_t *jl_gf_supertype_name(jl_sym_t *name) { - // type name is function name prefixed with # size_t l = strlen(jl_symbol_name(name)); char *prefixed; prefixed = (char*)malloc_s(l+2); @@ -3801,6 +3800,14 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ strcpy(&prefixed[1], jl_symbol_name(name)); jl_sym_t *tname = jl_symbol(prefixed); free(prefixed); + return tname; +} + +// Return value is rooted globally +jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st, size_t new_world) +{ + // type name is function name prefixed with # + jl_sym_t *tname = jl_gf_supertype_name(name); jl_datatype_t *ftype = (jl_datatype_t*)jl_new_datatype( tname, module, st, jl_emptysvec, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); @@ -3808,7 +3815,7 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ JL_GC_PUSH1(&ftype); ftype->name->mt->name = name; jl_gc_wb(ftype->name->mt, name); - jl_set_const(module, tname, (jl_value_t*)ftype); + jl_declare_constant_val3(NULL, module, tname, (jl_value_t*)ftype, BINDING_KIND_CONST, new_world); jl_value_t *f = jl_new_struct(ftype); ftype->instance = f; jl_gc_wb(ftype, f); @@ -3816,9 +3823,9 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ return (jl_function_t*)f; } -jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module) +jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module, size_t new_world) { - return jl_new_generic_function_with_supertype(name, module, jl_function_type); + return jl_new_generic_function_with_supertype(name, module, jl_function_type, new_world); } struct ml_matches_env { diff --git a/src/init.c b/src/init.c index ff07c3a9e26ab..f383a8f4620be 100644 --- a/src/init.c +++ b/src/init.c @@ -249,6 +249,8 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER } if (jl_base_module) { + size_t last_age = ct->world_age; + ct->world_age = jl_get_world_counter(); jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_atexit")); if (f != NULL) { jl_value_t **fargs; @@ -257,10 +259,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER fargs[1] = jl_box_int32(exitcode); JL_TRY { assert(ct); - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); jl_apply(fargs, 2); - ct->world_age = last_age; } JL_CATCH { jl_printf((JL_STREAM*)STDERR_FILENO, "\natexit hook threw an error: "); @@ -270,10 +269,15 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER } JL_GC_POP(); } + ct->world_age = last_age; } - if (ct && exitcode == 0) + if (ct && exitcode == 0) { + size_t last_age = ct->world_age; + ct->world_age = jl_get_world_counter(); jl_write_compiler_output(); + ct->world_age = last_age; + } jl_print_gc_stats(JL_STDERR); if (jl_options.code_coverage) @@ -893,6 +897,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_init_primitives(); jl_init_main_module(); jl_load(jl_core_module, "boot.jl"); + jl_current_task->world_age = jl_atomic_load_acquire(&jl_world_counter); post_boot_hooks(); } diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index b92380df7a49c..c1b29a091511b 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -449,7 +449,6 @@ XX(jl_tagged_gensym) \ XX(jl_take_buffer) \ XX(jl_task_get_next) \ - XX(jl_task_stack_buffer) \ XX(jl_termios_size) \ XX(jl_test_cpu_feature) \ XX(jl_threadid) \ diff --git a/src/jlapi.c b/src/jlapi.c index defb2db6ac911..b8fbda801f43b 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -910,26 +910,29 @@ static NOINLINE int true_main(int argc, char *argv[]) { jl_set_ARGS(argc, argv); + + jl_task_t *ct = jl_current_task; + size_t last_age = ct->world_age; + ct->world_age = jl_get_world_counter(); + jl_function_t *start_client = jl_base_module ? (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("_start")) : NULL; - jl_task_t *ct = jl_current_task; if (start_client) { int ret = 1; JL_TRY { - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); jl_value_t *r = jl_apply(&start_client, 1); if (jl_typeof(r) != (jl_value_t*)jl_int32_type) jl_type_error("typeassert", (jl_value_t*)jl_int32_type, r); ret = jl_unbox_int32(r); - ct->world_age = last_age; } JL_CATCH { jl_no_exc_handler(jl_current_exception(ct), ct); } + ct->world_age = last_age; return ret; } + ct->world_age = last_age; // run program if specified, otherwise enter REPL if (argc > 0) { diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 7fd2dc7409c0e..57f67755df692 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -433,7 +433,7 @@ (inert ,loc))) ,body)))) (if (or (symbol? name) (globalref? name)) - `(block ,@generator (method ,name) ,mdef (unnecessary ,name)) ;; return the function + `(block ,@generator (method ,name) (latestworld-if-toplevel) ,mdef (unnecessary ,name)) ;; return the function (if (not (null? generator)) `(block ,@generator ,mdef) mdef)))))) @@ -531,6 +531,7 @@ `(call (core ifelse) (false) (false) (block ;; forward-declare function so its type can occur in the signature of the inner method below ,@(if (or (symbol? name) (globalref? name)) `((method ,name)) '()) + (latestworld-if-toplevel) ;; call with keyword args pre-sorted - original method code goes here ,(method-def-expr- @@ -1515,6 +1516,7 @@ (scope-block (block (hardscope) (local (= ,(cadr arg) ,rr)) ,.(map (lambda (v) `(,(car e) (globalref (thismodule) ,v) ,v)) (filter-not-underscore (lhs-vars (cadr arg)))) + (latestworld) ,rr)))))))) (else (error "expected assignment after \"const\""))))))) @@ -2473,7 +2475,7 @@ (error "Opaque closure argument type may not be specified both in the method signature and separately")) (if (or (varargexpr? lastarg) (vararg? lastarg)) '(true) '(false)))) - (meth (caddr (caddr (expand-forms F)))) ;; `method` expr + (meth (cadddr (caddr (expand-forms F)))) ;; `method` expr (lam (cadddr meth)) (sig-block (caddr meth)) (sig-block (if (and (pair? sig-block) (eq? (car sig-block) 'block)) @@ -3154,6 +3156,11 @@ (else `(globalref (thismodule) ,e))))) ((or (not (pair? e)) (quoted? e) (memq (car e) '(toplevel symbolicgoto symboliclabel toplevel-only))) e) + ((eq? (car e) 'isglobal) + (let ((val (and scope (get (scope:table scope) (cadr e) #f)))) + (cond (val `(false)) + ((underscore-symbol? (cadr e)) `(false)) + (else `(true))))) ((eq? (car e) 'global) (check-valid-name (cadr e)) e) @@ -3793,7 +3800,7 @@ f(x) = yt(x) (Set '(quote top core lineinfo line inert local-def unnecessary copyast meta inbounds boundscheck loopinfo decl aliasscope popaliasscope thunk with-static-parameters toplevel-only - global globalref assign-const-if-global thismodule + global globalref assign-const-if-global isglobal thismodule const atomic null true false ssavalue isdefined toplevel module lambda error gc_preserve_begin gc_preserve_end import using export public inline noinline purity))) @@ -4296,6 +4303,7 @@ f(x) = yt(x) `(toplevel-butfirst ,(convert-assignment name mk-closure fname lam interp opaq parsed-method-stack globals locals) ,@typedef + (latestworld) ,@(map (lambda (v) `(moved-local ,v)) moved-vars) ,@sp-inits ,@mk-method diff --git a/src/julia.h b/src/julia.h index b2d72a7763d2b..1088f669ad773 100644 --- a/src/julia.h +++ b/src/julia.h @@ -72,21 +72,20 @@ typedef struct _jl_tls_states_t *jl_ptls_t; #endif #include "gc-interface.h" #include "julia_atomics.h" -#include "julia_threads.h" #include "julia_assert.h" +// the common fields are hidden before the pointer, but the following macro is +// used to indicate which types below are subtypes of jl_value_t +#define JL_DATA_TYPE +typedef struct _jl_value_t jl_value_t; +#include "julia_threads.h" + #ifdef __cplusplus extern "C" { #endif // core data types ------------------------------------------------------------ -// the common fields are hidden before the pointer, but the following macro is -// used to indicate which types below are subtypes of jl_value_t -#define JL_DATA_TYPE - -typedef struct _jl_value_t jl_value_t; - struct _jl_taggedvalue_bits { uintptr_t gc:2; uintptr_t in_image:1; @@ -484,9 +483,6 @@ typedef struct _jl_abi_override_t { jl_method_instance_t *def; } jl_abi_override_t; -// all values are callable as Functions -typedef jl_value_t jl_function_t; - typedef struct { JL_DATA_TYPE jl_sym_t *name; @@ -2009,7 +2005,6 @@ JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr); -JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr); JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr); JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); @@ -2266,12 +2261,8 @@ JL_DLLEXPORT void jl_sigatomic_end(void); // tasks and exceptions ------------------------------------------------------- -typedef struct _jl_timing_block_t jl_timing_block_t; -typedef struct _jl_timing_event_t jl_timing_event_t; -typedef struct _jl_excstack_t jl_excstack_t; - // info describing an exception handler -typedef struct _jl_handler_t { +struct _jl_handler_t { jl_jmp_buf eh_ctx; jl_gcframe_t *gcstack; jl_value_t *scope; @@ -2281,68 +2272,7 @@ typedef struct _jl_handler_t { sig_atomic_t defer_signal; jl_timing_block_t *timing_stack; size_t world_age; -} jl_handler_t; - -#define JL_RNG_SIZE 5 // xoshiro 4 + splitmix 1 - -typedef struct _jl_task_t { - JL_DATA_TYPE - jl_value_t *next; // invasive linked list for scheduler - jl_value_t *queue; // invasive linked list for scheduler - jl_value_t *tls; - jl_value_t *donenotify; - jl_value_t *result; - jl_value_t *scope; - jl_function_t *start; - _Atomic(uint8_t) _state; - uint8_t sticky; // record whether this Task can be migrated to a new thread - uint16_t priority; - _Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with - uint8_t pad0[3]; - // === 64 bytes (cache line) - uint64_t rngState[JL_RNG_SIZE]; - // flag indicating whether or not to record timing metrics for this task - uint8_t metrics_enabled; - uint8_t pad1[3]; - // timestamp this task first entered the run queue - _Atomic(uint64_t) first_enqueued_at; - // timestamp this task was most recently scheduled to run - _Atomic(uint64_t) last_started_running_at; - // time this task has spent running; updated when it yields or finishes. - _Atomic(uint64_t) running_time_ns; - // === 64 bytes (cache line) - // timestamp this task finished (i.e. entered state DONE or FAILED). - _Atomic(uint64_t) finished_at; - -// hidden state: - - // id of owning thread - does not need to be defined until the task runs - _Atomic(int16_t) tid; - // threadpool id - int8_t threadpoolid; - // Reentrancy bits - // Bit 0: 1 if we are currently running inference/codegen - // Bit 1-2: 0-3 counter of how many times we've reentered inference - // Bit 3: 1 if we are writing the image and inference is illegal - uint8_t reentrant_timing; - // 2 bytes of padding on 32-bit, 6 bytes on 64-bit - // uint16_t padding2_32; - // uint48_t padding2_64; - // saved gc stack top for context switches - jl_gcframe_t *gcstack; - size_t world_age; - // quick lookup for current ptls - jl_ptls_t ptls; // == jl_all_tls_states[tid] -#ifdef USE_TRACY - const char *name; -#endif - // saved exception stack - jl_excstack_t *excstack; - // current exception handler - jl_handler_t *eh; - // saved thread state - jl_ucontext_t ctx; // pointer into stkbuf, if suspended -} jl_task_t; +}; #define JL_TASK_STATE_RUNNABLE 0 #define JL_TASK_STATE_DONE 1 diff --git a/src/julia_gcext.h b/src/julia_gcext.h index 05140e4b09ace..e124f58a09402 100644 --- a/src/julia_gcext.h +++ b/src/julia_gcext.h @@ -135,15 +135,6 @@ JL_DLLEXPORT int jl_gc_conservative_gc_support_enabled(void); // NOTE: Only valid to call from within a GC context. JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p) JL_NOTSAFEPOINT; -// Return a non-null pointer to the start of the stack area if the task -// has an associated stack buffer. In that case, *size will also contain -// the size of that stack buffer upon return. Also, if task is a thread's -// current task, that thread's id will be stored in *tid; otherwise, -// *tid will be set to -1. -// -// DEPRECATED: use jl_active_task_stack() instead. -JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *tid); - // Query the active and total stack range for the given task, and set // *active_start and *active_end respectively *total_start and *total_end // accordingly. The range for the active part is a best-effort approximation diff --git a/src/julia_internal.h b/src/julia_internal.h index 3e4967c9d4dca..0da6d412c8a49 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -828,8 +828,8 @@ jl_value_t *modify_value(jl_value_t *ty, _Atomic(jl_value_t*) *p, jl_value_t *pa jl_value_t *modify_bits(jl_value_t *ty, char *p, uint8_t *psel, jl_value_t *parent, jl_value_t *op, jl_value_t *rhs, enum atomic_kind isatomic); int setonce_bits(jl_datatype_t *rty, char *p, jl_value_t *owner, jl_value_t *rhs, enum atomic_kind isatomic); jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); -jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); -jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st); +jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module, size_t new_world); +jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st, size_t new_world); int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), void *env); int foreach_mtable_in_module(jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env); void jl_init_main_module(void); @@ -842,6 +842,7 @@ JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t void jl_binding_set_type(jl_binding_t *b, jl_module_t *mod, jl_sym_t *sym, jl_value_t *ty); void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); JL_DLLEXPORT void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type); +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno); STATIC_INLINE struct _jl_module_using *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; @@ -985,15 +986,15 @@ STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFE return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED; } -JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world); -JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b JL_PROPAGATES_ROOT, size_t min_world, size_t max_world); +JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world) JL_GLOBALLY_ROOTED; +JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_GLOBALLY_ROOTED; EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT { return decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); } -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world) JL_NOTSAFEPOINT; -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; +STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; +STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; #ifndef __clang_analyzer__ STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world) JL_NOTSAFEPOINT @@ -1669,6 +1670,7 @@ JL_DLLEXPORT void jl_set_next_task(jl_task_t *task) JL_NOTSAFEPOINT; // -- synchronization utilities -- // extern jl_mutex_t typecache_lock; +extern jl_mutex_t world_counter_lock; #if defined(__APPLE__) void jl_mach_gc_end(void) JL_NOTSAFEPOINT; diff --git a/src/julia_threads.h b/src/julia_threads.h index b6ef65dc7fe52..061eb9266e7a7 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -218,6 +218,76 @@ typedef struct _jl_tls_states_t { #endif } jl_tls_states_t; +#define JL_RNG_SIZE 5 // xoshiro 4 + splitmix 1 + +// all values are callable as Functions +typedef jl_value_t jl_function_t; + +typedef struct _jl_timing_block_t jl_timing_block_t; +typedef struct _jl_timing_event_t jl_timing_event_t; +typedef struct _jl_excstack_t jl_excstack_t; + +typedef struct _jl_handler_t jl_handler_t; + +typedef struct _jl_task_t { + JL_DATA_TYPE + jl_value_t *next; // invasive linked list for scheduler + jl_value_t *queue; // invasive linked list for scheduler + jl_value_t *tls; + jl_value_t *donenotify; + jl_value_t *result; + jl_value_t *scope; + jl_function_t *start; + _Atomic(uint8_t) _state; + uint8_t sticky; // record whether this Task can be migrated to a new thread + uint16_t priority; + _Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with + uint8_t pad0[3]; + // === 64 bytes (cache line) + uint64_t rngState[JL_RNG_SIZE]; + // flag indicating whether or not to record timing metrics for this task + uint8_t metrics_enabled; + uint8_t pad1[3]; + // timestamp this task first entered the run queue + _Atomic(uint64_t) first_enqueued_at; + // timestamp this task was most recently scheduled to run + _Atomic(uint64_t) last_started_running_at; + // time this task has spent running; updated when it yields or finishes. + _Atomic(uint64_t) running_time_ns; + // === 64 bytes (cache line) + // timestamp this task finished (i.e. entered state DONE or FAILED). + _Atomic(uint64_t) finished_at; + +// hidden state: + + // id of owning thread - does not need to be defined until the task runs + _Atomic(int16_t) tid; + // threadpool id + int8_t threadpoolid; + // Reentrancy bits + // Bit 0: 1 if we are currently running inference/codegen + // Bit 1-2: 0-3 counter of how many times we've reentered inference + // Bit 3: 1 if we are writing the image and inference is illegal + uint8_t reentrant_timing; + // 2 bytes of padding on 32-bit, 6 bytes on 64-bit + // uint16_t padding2_32; + // uint48_t padding2_64; + // saved gc stack top for context switches + jl_gcframe_t *gcstack; + size_t world_age; + // quick lookup for current ptls + jl_ptls_t ptls; // == jl_all_tls_states[tid] +#ifdef USE_TRACY + const char *name; +#endif + // saved exception stack + jl_excstack_t *excstack; + // current exception handler + jl_handler_t *eh; + // saved thread state + jl_ucontext_t ctx; // pointer into stkbuf, if suspended +} jl_task_t; + JL_DLLEXPORT void *jl_get_ptls_states(void); // Update codegen version in `ccall.cpp` after changing either `pause` or `wake` diff --git a/src/method.c b/src/method.c index 0a58f0d5c482c..4b39de9aa67e1 100644 --- a/src/method.c +++ b/src/method.c @@ -1073,16 +1073,28 @@ JL_DLLEXPORT void jl_check_gf(jl_value_t *gf, jl_sym_t *name) JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, jl_sym_t *name) { - jl_value_t *gf = jl_get_binding_value_if_const(b); - if (gf) { - jl_check_gf(gf, b->globalref->name); - return gf; - } - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - if (!jl_bkind_is_some_guard(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))) + JL_LOCK(&world_counter_lock); + size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, new_world); + jl_value_t *gf = NULL; + if (!jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + gf = decode_restriction_value(pku); + JL_GC_PROMISE_ROOTED(gf); + jl_check_gf(gf, b->globalref->name); + JL_UNLOCK(&world_counter_lock); + return gf; + } jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); - gf = (jl_value_t*)jl_new_generic_function(name, mod); - jl_declare_constant_val(b, mod, name, gf); + } + gf = (jl_value_t*)jl_new_generic_function(name, mod, new_world); + // From this point on (if we didn't error), we're committed to raising the world age, + // because we've used it to declare the type name. + jl_atomic_store_release(&jl_world_counter, new_world); + jl_declare_constant_val3(b, mod, name, gf, BINDING_KIND_CONST, new_world); + JL_GC_PROMISE_ROOTED(gf); + JL_UNLOCK(&world_counter_lock); return gf; } diff --git a/src/module.c b/src/module.c index 66049031f8790..be6779727bfdc 100644 --- a/src/module.c +++ b/src/module.c @@ -411,13 +411,13 @@ typedef struct _modstack_t { jl_sym_t *var; struct _modstack_t *prev; } modstack_t; -static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st); +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st, size_t world); JL_DLLEXPORT jl_value_t *jl_reresolve_binding_value_seqcst(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (jl_bkind_is_some_guard(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))) { - jl_resolve_owner(b, b->globalref->mod, b->globalref->name, NULL); + jl_resolve_owner(b, b->globalref->mod, b->globalref->name, NULL, jl_current_task->world_age); } return jl_get_binding_value_seqcst(b); } @@ -473,7 +473,7 @@ static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_ } // find a binding from a module's `usings` list -static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn) +static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn, size_t world) { jl_binding_t *b = NULL; jl_binding_partition_t *bpart = NULL; @@ -487,20 +487,20 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl JL_UNLOCK(&m->lock); jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); if (tempb != NULL && tempb->exportp) { - tempb = jl_resolve_owner(NULL, imp, var, st); // find the owner for tempb + tempb = jl_resolve_owner(NULL, imp, var, st, world); // find the owner for tempb if (tempb == NULL) // couldn't resolve; try next using (see issue #6105) continue; - jl_binding_partition_t *tempbpart = jl_get_binding_partition(tempb, jl_current_task->world_age); + jl_binding_partition_t *tempbpart = jl_get_binding_partition(tempb, world); jl_ptr_kind_union_t tempb_pku = jl_atomic_load_relaxed(&tempbpart->restriction); - assert(decode_restriction_kind(tempb_pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(tempb_pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(tempb_pku))); + assert(jl_bkind_is_some_guard(decode_restriction_kind(tempb_pku)) || decode_restriction_kind(tempb_pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(tempb_pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(tempb_pku))); (void)tempb_pku; - if (bpart != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempbpart, b, jl_current_task->world_age)) { + if (bpart != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempbpart, b, world)) { if (warn) { // set usingfailed=1 to avoid repeating this warning // the owner will still be NULL, so it can be later imported or defined tempb = jl_get_module_binding(m, var, 1); - tempbpart = jl_get_binding_partition(tempb, jl_current_task->world_age); + tempbpart = jl_get_binding_partition(tempb, world); jl_atomic_store_release(&tempbpart->restriction, encode_restriction(NULL, BINDING_KIND_FAILED)); } return NULL; @@ -524,7 +524,7 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) != BINDING_KIND_GLOBAL) { // for implicitly imported globals, try to re-resolve it to find the module we got it from most directly jl_module_t *from = NULL; - jl_binding_t *b2 = using_resolve_binding(m, var, &from, NULL, 0); + jl_binding_t *b2 = using_resolve_binding(m, var, &from, NULL, 0, jl_current_task->world_age); if (b2) { jl_binding_partition_t *b2part = jl_get_binding_partition(b2, jl_current_task->world_age); if (eq_bindings(b2part, b, jl_current_task->world_age)) @@ -538,11 +538,11 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b); // get binding for reading. might return NULL for unbound. -static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st) +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st, size_t world) { if (b == NULL) b = jl_get_module_binding(m, var, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); retry: if (decode_restriction_kind(pku) == BINDING_KIND_FAILED) @@ -561,7 +561,7 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * } } jl_module_t *from = NULL; // for error message printing - b2 = using_resolve_binding(m, var, &from, &top, 1); + b2 = using_resolve_binding(m, var, &from, &top, 1, world); if (b2 == NULL) return NULL; assert(from); @@ -594,7 +594,7 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * } return b2; } - jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + jl_walk_binding_inplace(&b, &bpart, world); return b; } @@ -606,7 +606,7 @@ JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) jl_module_t *from = m; jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); if (decode_restriction_kind(pku) == BINDING_KIND_GUARD) { - b = using_resolve_binding(m, var, &from, NULL, 0); + b = using_resolve_binding(m, var, &from, NULL, 0, jl_current_task->world_age); bpart = jl_get_binding_partition(b, jl_current_task->world_age); } pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); @@ -637,7 +637,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { - return jl_resolve_owner(NULL, m, var, NULL); + return jl_resolve_owner(NULL, m, var, NULL, jl_current_task->world_age); } JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var) @@ -1001,7 +1001,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); // ignores b->deprecated return b == NULL ? NULL : jl_get_binding_value(b); } @@ -1028,6 +1028,7 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var // this function is mostly only used during initialization, so the data races here are not too important to us jl_binding_t *bp = jl_get_module_binding(m, var, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age); + assert(jl_bkind_is_some_guard(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))); jl_atomic_store_release(&bpart->restriction, encode_restriction(val, BINDING_KIND_CONST)); jl_gc_wb(bpart, val); } @@ -1047,11 +1048,10 @@ void jl_invalidate_binding_refs(jl_globalref_t *ref, jl_binding_partition_t *inv JL_GC_POP(); } -extern jl_mutex_t world_counter_lock; JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GUARD) { @@ -1074,18 +1074,17 @@ JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (!bpart) return 0; return jl_bkind_is_some_constant(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction))); } -JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr) +JL_DLLEXPORT void jl_force_binding_resolution(jl_globalref_t *gr, size_t world) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL); - return b && jl_get_binding_value(b) != NULL; + jl_resolve_owner(b, gr->mod, gr->name, NULL, world); } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 1790b9bd8d106..49d510cc48c34 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -812,7 +812,6 @@ static void jl_##name##16(unsigned runtime_nbits, void *pa, void *pb, void *pr) runtime_nbits = 16; \ float R = OP(A, B); \ *(uint16_t*)pr = float_to_half(R); \ - *(uint16_t*)pr = float_to_half(R); \ } #define bi_intrinsic_bfloat(OP, name) \ @@ -903,7 +902,6 @@ static void jl_##name##16(unsigned runtime_nbits, void *pa, void *pb, void *pc, runtime_nbits = 16; \ float R = OP(A, B, C); \ *(uint16_t*)pr = float_to_half(R); \ - *(uint16_t*)pr = float_to_half(R); \ } #define ter_intrinsic_bfloat(OP, name) \ @@ -1426,17 +1424,17 @@ bi_fintrinsic(_min, min_float) float max_float(float x, float y) JL_NOTSAFEPOINT { float diff = x - y; - float argmin = signbit(diff) ? y : x; + float argmax = signbit(diff) ? y : x; int is_nan = isnan(x) || isnan(y); - return is_nan ? diff : argmin; + return is_nan ? diff : argmax; } double max_double(double x, double y) JL_NOTSAFEPOINT { double diff = x - y; - double argmin = signbit(diff) ? x : y; + double argmax = signbit(diff) ? y : x; int is_nan = isnan(x) || isnan(y); - return is_nan ? diff : argmin; + return is_nan ? diff : argmax; } #define _max(a, b) sizeof(a) == sizeof(float) ? max_float(a, b) : max_double(a, b) diff --git a/src/signals-unix.c b/src/signals-unix.c index 788539b1f5096..c730f27f16def 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -1208,7 +1208,7 @@ void jl_install_default_signal_handlers(void) memset(&act, 0, sizeof(struct sigaction)); sigemptyset(&act.sa_mask); act.sa_sigaction = usr2_handler; - act.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART; + act.sa_flags = SA_SIGINFO | SA_RESTART; if (sigaction(SIGUSR2, &act, NULL) < 0) { jl_errorf("fatal error: sigaction: %s", strerror(errno)); } diff --git a/src/staticdata.c b/src/staticdata.c index 7fad87652b26a..ff352cd8c152f 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1674,8 +1674,18 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED #ifndef _P64 write_uint(f, decode_restriction_kind(pku)); #endif - write_uint(f, bpart->min_world); - write_uint(f, jl_atomic_load_relaxed(&bpart->max_world)); + size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); + if (max_world == ~(size_t)0) { + // Still valid. Will be considered primordial after re-load. + // We could consider updating min_world to the loaded world, but + // there doesn't appear to be much point. + write_uint(f, 0); + write_uint(f, max_world); + } else { + // The world will not be reachable after loading + write_uint(f, 1); + write_uint(f, 0); + } write_pointerfield(s, (jl_value_t*)jl_atomic_load_relaxed(&bpart->next)); #ifdef _P64 write_uint(f, decode_restriction_kind(pku)); // This will be moved back into place during deserialization (if necessary) @@ -2714,7 +2724,6 @@ static void jl_strip_all_codeinfos(void) jl_genericmemory_t *jl_global_roots_list; jl_genericmemory_t *jl_global_roots_keyset; jl_mutex_t global_roots_lock; -extern jl_mutex_t world_counter_lock; jl_mutex_t precompile_field_replace_lock; jl_svec_t *precompile_field_replace JL_GLOBALLY_ROOTED; diff --git a/src/task.c b/src/task.c index d56d60eb58cb5..37e7f0e1f5440 100644 --- a/src/task.c +++ b/src/task.c @@ -352,34 +352,6 @@ void JL_NORETURN jl_finish_task(jl_task_t *ct) abort(); } -JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *ptid) -{ - size_t off = 0; -#ifndef _OS_WINDOWS_ - jl_ptls_t ptls0 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; - if (ptls0->root_task == task) { - // See jl_init_root_task(). The root task of the main thread - // has its buffer enlarged by an artificial 3000000 bytes, but - // that means that the start of the buffer usually points to - // inaccessible memory. We need to correct for this. - off = ROOT_TASK_STACK_ADJUSTMENT; - } -#endif - jl_ptls_t ptls2 = task->ptls; - *ptid = -1; - if (ptls2) { - *ptid = jl_atomic_load_relaxed(&task->tid); -#ifdef COPY_STACKS - if (task->ctx.copy_stack) { - *size = ptls2->stacksize; - return (char *)ptls2->stackbase - *size; - } -#endif - } - *size = task->ctx.bufsz - off; - return (void *)((char *)task->ctx.stkbuf + off); -} - JL_DLLEXPORT void jl_active_task_stack(jl_task_t *task, char **active_start, char **active_end, char **total_start, char **total_end) diff --git a/src/toplevel.c b/src/toplevel.c index fb217ec7cb52e..dee9029e2feb7 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -66,14 +66,13 @@ void jl_module_run_initializer(jl_module_t *m) { JL_TIMING(INIT_MODULE, INIT_MODULE); jl_timing_show_module(m, JL_TIMING_DEFAULT_BLOCK); - jl_function_t *f = jl_module_get_initializer(m); - if (f == NULL) - return; jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; JL_TRY { ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - jl_apply(&f, 1); + jl_function_t *f = jl_module_get_initializer(m); + if (f != NULL) + jl_apply(&f, 1); ct->world_age = last_age; } JL_CATCH { @@ -740,10 +739,15 @@ static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, con JL_GC_POP(); } -JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, enum jl_partition_kind constant_kind) +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( + jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, + enum jl_partition_kind constant_kind, size_t new_world) { JL_GC_PUSH1(&val); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + if (!b) { + b = jl_get_module_binding(mod, var, 1); + } + jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); int did_warn = 0; while (1) { @@ -780,10 +784,27 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b, j break; } } + // N.B.: This backdates the first definition of the constant to world age 0 for backwards compatibility + // TODO: Mark this specially with a separate partition. + if (bpart->min_world != 0) + bpart->min_world = new_world; JL_GC_POP(); return bpart; } +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2( + jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, + enum jl_partition_kind constant_kind) +{ + JL_LOCK(&world_counter_lock); + size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; + jl_binding_partition_t *bpart = jl_declare_constant_val3(b, mod, var, val, constant_kind, new_world); + if (bpart->min_world == new_world) + jl_atomic_store_release(&jl_world_counter, new_world); + JL_UNLOCK(&world_counter_lock); + return bpart; +} + JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val) { return jl_declare_constant_val2(b, mod, var, val, val ? BINDING_KIND_CONST : BINDING_KIND_UNDEF_CONST); @@ -869,6 +890,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val else if (head == jl_using_sym) { jl_sym_t *name = NULL; jl_module_t *from = eval_import_from(m, ex, "using"); + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); size_t i = 0; if (from) { i = 1; @@ -908,6 +930,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val jl_expr_t *path = (jl_expr_t*)jl_exprarg(a, 0); name = NULL; jl_module_t *import = eval_import_path(m, from, ((jl_expr_t*)path)->args, &name, "using"); + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); assert(name); check_macro_rename(name, asname, "using"); // `using A: B as C` syntax @@ -919,11 +942,13 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val "syntax: malformed \"using\" statement"); } JL_GC_POP(); + ct->world_age = last_age; return jl_nothing; } else if (head == jl_import_sym) { jl_sym_t *name = NULL; jl_module_t *from = eval_import_from(m, ex, "import"); + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); size_t i = 0; if (from) { i = 1; @@ -967,6 +992,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val "syntax: malformed \"import\" statement"); } JL_GC_POP(); + ct->world_age = last_age; return jl_nothing; } else if (head == jl_export_sym || head == jl_public_sym) { diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index e8aa1188ec213..26ad6651cd880 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -639,18 +639,24 @@ function is_call_graph_uncached(sv::CC.InferenceState) return is_call_graph_uncached(parent::CC.InferenceState) end -isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) - # aggressive global binding resolution within `repl_frame` function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool, sv::CC.InferenceState) + # Ignore saw_latestworld + partition = CC.abstract_eval_binding_partition!(interp, g, sv) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) - if isdefined_globalref(g) + if CC.is_defined_const_binding(CC.binding_kind(partition)) return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}( - CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL), nothing) + CC.RTEffects(Const(CC.partition_restriction(partition)), Union{}, CC.EFFECTS_TOTAL), partition) + else + b = convert(Core.Binding, g) + if CC.binding_kind(partition) == CC.BINDING_KIND_GLOBAL && isdefined(b, :value) + return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}( + CC.RTEffects(Const(b.value), Union{}, CC.EFFECTS_TOTAL), partition) + end end return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}( - CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), nothing) + CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), partition) end return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool, sv::CC.InferenceState) @@ -1091,6 +1097,9 @@ function complete_keyword_argument(partial::String, last_idx::Int, context_modul last_word = partial[wordrange] # the word to complete kwargs = Set{String}() for m in methods + # if MAX_METHOD_COMPLETIONS is hit a single TextCompletion is return by complete_methods! with an explanation + # which can be ignored here + m isa TextCompletion && continue m::MethodCompletion possible_kwargs = Base.kwarg_decl(m.method) current_kwarg_candidates = String[] diff --git a/stdlib/REPL/src/precompile.jl b/stdlib/REPL/src/precompile.jl index daa01f626aeab..4f3b3a6eae083 100644 --- a/stdlib/REPL/src/precompile.jl +++ b/stdlib/REPL/src/precompile.jl @@ -8,6 +8,7 @@ import ..REPL Base._track_dependencies[] = false try Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testhelpers", "FakePTYs.jl")) + @Core.latestworld import .FakePTYs: open_fake_pty finally Base._track_dependencies[] = true diff --git a/stdlib/REPL/test/precompilation.jl b/stdlib/REPL/test/precompilation.jl index 7efcf0b5e8282..01a062644596c 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 = 0 + expected_precompiles = 1 n_precompiles = count(r"precompile\(", tracecompile_out) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 809913502c3d7..8944fd76f31de 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -769,9 +769,9 @@ fake_repl() do stdin_write, stdout_read, repl julia> A = 3\e[201~ """) @test Main.A == 3 - @test Base.invokelatest(Main.foo, 4) - @test Base.invokelatest(Main.T17599, 3).a == 3 - @test !Base.invokelatest(Main.foo, 2) + @test @invokelatest(Main.foo(4)) + @test @invokelatest(Main.T17599(3)).a == 3 + @test !@invokelatest(Main.foo(2)) sendrepl2("""\e[200~ julia> goo(x) = x + 1 @@ -781,7 +781,7 @@ fake_repl() do stdin_write, stdout_read, repl 4\e[201~ """) @test Main.A == 4 - @test Base.invokelatest(Main.goo, 4) == 5 + @test @invokelatest(Main.goo(4)) == 5 # Test prefix removal only active in bracket paste mode sendrepl2("julia = 4\n julia> 3 && (A = 1)\n") diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index 4d9b439e639d7..f1b83ca947c7e 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -130,7 +130,7 @@ create_serialization_stream() do s # user-defined module modtype = eval(Meta.parse("$(modstring)")) serialize(s, modtype) seek(s, 0) - @test deserialize(s) === modtype + @test invokelatest(deserialize, s) === modtype end # DataType @@ -151,7 +151,7 @@ create_serialization_stream() do s # user-defined type utype = eval(Meta.parse("$(usertype)")) serialize(s, utype) seek(s, 0) - @test deserialize(s) === utype + @test invokelatest(deserialize, s) === utype end create_serialization_stream() do s # user-defined type @@ -160,7 +160,7 @@ create_serialization_stream() do s # user-defined type utype = eval(Meta.parse("$(usertype)")) serialize(s, utype) seek(s, 0) - @test deserialize(s) === utype + @test invokelatest(deserialize, s) === utype end create_serialization_stream() do s # user-defined type @@ -169,7 +169,7 @@ create_serialization_stream() do s # user-defined type utype = eval(Meta.parse("$(usertype)")) serialize(s, utype) seek(s, 0) - @test deserialize(s) == utype + @test invokelatest(deserialize, s) == utype end create_serialization_stream() do s # immutable struct with 1 field @@ -178,7 +178,7 @@ create_serialization_stream() do s # immutable struct with 1 field utype = eval(Meta.parse("$(usertype)")) serialize(s, utype) seek(s, 0) - @test deserialize(s) == utype + @test invokelatest(deserialize, s) == utype end create_serialization_stream() do s # immutable struct with 2 field @@ -187,7 +187,7 @@ create_serialization_stream() do s # immutable struct with 2 field utval = eval(Meta.parse("$(usertype)(1,2)")) serialize(s, utval) seek(s, 0) - @test deserialize(s) === utval + @test invokelatest(deserialize, s) === utval end create_serialization_stream() do s # immutable struct with 3 field @@ -196,7 +196,7 @@ create_serialization_stream() do s # immutable struct with 3 field utval = eval(Meta.parse("$(usertype)(1,2,3)")) serialize(s, utval) seek(s, 0) - @test deserialize(s) === utval + @test invokelatest(deserialize, s) === utval end create_serialization_stream() do s # immutable struct with 4 field @@ -205,7 +205,7 @@ create_serialization_stream() do s # immutable struct with 4 field utval = eval(Meta.parse("$(usertype)(1,2,3,4)")) serialize(s, utval) seek(s, 0) - @test deserialize(s) === utval + @test invokelatest(deserialize, s) === utval end # Expression diff --git a/test/core.jl b/test/core.jl index 4bbb2ca368019..3886e6728df10 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2622,7 +2622,7 @@ end # issue #8338 let ex = Expr(:(=), :(f8338(x;y=4)), :(x*y)) eval(ex) - @test invokelatest(f8338, 2) == 8 + @test (@invokelatest f8338(2)) == 8 end # call overloading (#2403) diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 7a63cd1c0a62e..bc1838ce2c68b 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -147,9 +147,81 @@ macro test_intrinsic(intr, args...) end end +@testset "Float64 intrinsics" begin + # unary + @test_intrinsic Core.Intrinsics.abs_float Float64(-3.3) Float64(3.3) + @test_intrinsic Core.Intrinsics.neg_float Float64(3.3) Float64(-3.3) + @test_intrinsic Core.Intrinsics.fpext Float64 Float64(3.3) Float64(3.3) + + # binary + @test_intrinsic Core.Intrinsics.add_float Float64(3.3) Float64(2) Float64(5.3) + @test_intrinsic Core.Intrinsics.sub_float Float64(3.3) Float64(2) Float64(1.2999999999999998) + @test_intrinsic Core.Intrinsics.mul_float Float64(3.3) Float64(2) Float64(6.6) + @test_intrinsic Core.Intrinsics.div_float Float64(3.3) Float64(2) Float64(1.65) + @test_intrinsic Core.Intrinsics.max_float Float64(1.0) Float64(2.0) Float64(2.0) + @test_intrinsic Core.Intrinsics.min_float Float64(1.0) Float64(2.0) Float64(1.0) + + # ternary + @test_intrinsic Core.Intrinsics.fma_float Float64(3.3) Float64(4.4) Float64(5.5) Float64(20.02) + @test_intrinsic Core.Intrinsics.muladd_float Float64(3.3) Float64(4.4) Float64(5.5) Float64(20.02) + + # boolean + @test_intrinsic Core.Intrinsics.eq_float Float64(3.3) Float64(3.3) true + @test_intrinsic Core.Intrinsics.eq_float Float64(3.3) Float64(2) false + @test_intrinsic Core.Intrinsics.ne_float Float64(3.3) Float64(3.3) false + @test_intrinsic Core.Intrinsics.ne_float Float64(3.3) Float64(2) true + @test_intrinsic Core.Intrinsics.le_float Float64(3.3) Float64(3.3) true + @test_intrinsic Core.Intrinsics.le_float Float64(3.3) Float64(2) false + + # conversions + @test_intrinsic Core.Intrinsics.sitofp Float64 3 Float64(3.0) + @test_intrinsic Core.Intrinsics.uitofp Float64 UInt(3) Float64(3.0) + @test_intrinsic Core.Intrinsics.fptosi Int Float64(3.3) 3 + @test_intrinsic Core.Intrinsics.fptoui UInt Float64(3.3) UInt(3) +end + +@testset "Float32 intrinsics" begin + # unary + @test_intrinsic Core.Intrinsics.abs_float Float32(-3.3) Float32(3.3) + @test_intrinsic Core.Intrinsics.neg_float Float32(3.3) Float32(-3.3) + @test_intrinsic Core.Intrinsics.fpext Float32 Float32(3.3) Float32(3.3) + @test_intrinsic Core.Intrinsics.fpext Float64 Float32(3.3) 3.299999952316284 + @test_intrinsic Core.Intrinsics.fptrunc Float32 Float64(3.3) Float32(3.3) + + # binary + @test_intrinsic Core.Intrinsics.add_float Float32(3.3) Float32(2) Float32(5.3) + @test_intrinsic Core.Intrinsics.sub_float Float32(3.3) Float32(2) Float32(1.3) + @test_intrinsic Core.Intrinsics.mul_float Float32(3.3) Float32(2) Float32(6.6) + @test_intrinsic Core.Intrinsics.div_float Float32(3.3) Float32(2) Float32(1.65) + @test_intrinsic Core.Intrinsics.max_float Float32(1.0) Float32(2.0) Float32(2.0) + @test_intrinsic Core.Intrinsics.min_float Float32(1.0) Float32(2.0) Float32(1.0) + + # ternary + @test_intrinsic Core.Intrinsics.fma_float Float32(3.3) Float32(4.4) Float32(5.5) Float32(20.02) + @test_intrinsic Core.Intrinsics.muladd_float Float32(3.3) Float32(4.4) Float32(5.5) Float32(20.02) + + # boolean + @test_intrinsic Core.Intrinsics.eq_float Float32(3.3) Float32(3.3) true + @test_intrinsic Core.Intrinsics.eq_float Float32(3.3) Float32(2) false + @test_intrinsic Core.Intrinsics.ne_float Float32(3.3) Float32(3.3) false + @test_intrinsic Core.Intrinsics.ne_float Float32(3.3) Float32(2) true + @test_intrinsic Core.Intrinsics.le_float Float32(3.3) Float32(3.3) true + @test_intrinsic Core.Intrinsics.le_float Float32(3.3) Float32(2) false + + # conversions + @test_intrinsic Core.Intrinsics.sitofp Float32 3 Float32(3.0) + @test_intrinsic Core.Intrinsics.uitofp Float32 UInt(3) Float32(3.0) + @test_intrinsic Core.Intrinsics.fptosi Int Float32(3.3) 3 + @test_intrinsic Core.Intrinsics.fptoui UInt Float32(3.3) UInt(3) +end + @testset "Float16 intrinsics" begin # unary + @test_intrinsic Core.Intrinsics.abs_float Float16(-3.3) Float16(3.3) @test_intrinsic Core.Intrinsics.neg_float Float16(3.3) Float16(-3.3) + # See + #broken @test_intrinsic Core.Intrinsics.fpext Float16 Float16(3.3) Float16(3.3) + @test_broken Core.Intrinsics.fpext(Float16, Float16(3.3)) === Float16(3.3) @test_intrinsic Core.Intrinsics.fpext Float32 Float16(3.3) 3.3007812f0 @test_intrinsic Core.Intrinsics.fpext Float64 Float16(3.3) 3.30078125 @test_intrinsic Core.Intrinsics.fptrunc Float16 Float32(3.3) Float16(3.3) @@ -160,6 +232,8 @@ end @test_intrinsic Core.Intrinsics.sub_float Float16(3.3) Float16(2) Float16(1.301) @test_intrinsic Core.Intrinsics.mul_float Float16(3.3) Float16(2) Float16(6.6) @test_intrinsic Core.Intrinsics.div_float Float16(3.3) Float16(2) Float16(1.65) + @test_intrinsic Core.Intrinsics.max_float Float16(1.0) Float16(2.0) Float16(2.0) + @test_intrinsic Core.Intrinsics.min_float Float16(1.0) Float16(2.0) Float16(1.0) # ternary @test_intrinsic Core.Intrinsics.fma_float Float16(3.3) Float16(4.4) Float16(5.5) Float16(20.02) @@ -174,8 +248,8 @@ end @test_intrinsic Core.Intrinsics.le_float Float16(3.3) Float16(2) false # conversions - @test_intrinsic Core.Intrinsics.sitofp Float16 3 Float16(3f0) - @test_intrinsic Core.Intrinsics.uitofp Float16 UInt(3) Float16(3f0) + @test_intrinsic Core.Intrinsics.sitofp Float16 3 Float16(3.0) + @test_intrinsic Core.Intrinsics.uitofp Float16 UInt(3) Float16(3.0) @test_intrinsic Core.Intrinsics.fptosi Int Float16(3.3) 3 @test_intrinsic Core.Intrinsics.fptoui UInt Float16(3.3) UInt(3) end diff --git a/test/loading.jl b/test/loading.jl index 09f96e1f43578..be8f08b4bfe22 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -798,6 +798,7 @@ end @testset "`::AbstractString` constraint on the path argument to `include`" begin for m ∈ (NotPkgModule, evalfile("testhelpers/just_module.jl")) + @Core.latestworld let i = m.include @test !applicable(i, (nothing,)) @test !applicable(i, (identity, nothing,)) diff --git a/test/precompile.jl b/test/precompile.jl index 78a96250600a4..f5a412b416ddc 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -610,13 +610,17 @@ precompile_test_harness(false) do dir @eval using UseBaz @test haskey(Base.loaded_modules, Base.PkgId("UseBaz")) @test haskey(Base.loaded_modules, Base.PkgId("Baz")) - @test Base.invokelatest(UseBaz.biz) === 1 - @test Base.invokelatest(UseBaz.buz) === 2 - @test UseBaz.generating == 0 - @test UseBaz.incremental == 0 + invokelatest() do + @test UseBaz.biz() === 1 + @test UseBaz.buz() === 2 + @test UseBaz.generating == 0 + @test UseBaz.incremental == 0 + end @eval using Baz - @test Base.invokelatest(Baz.baz) === 1 - @test Baz === UseBaz.Baz + invokelatest() do + @test Baz.baz() === 1 + @test Baz === UseBaz.Baz + end # should not throw if the cachefile does not exist @test !isfile("DoesNotExist.ji") @@ -1094,6 +1098,17 @@ precompile_test_harness("invoke") do dir f44320(::Any) = 2 g44320() = invoke(f44320, Tuple{Any}, 0) g44320() + # Issue #57115 + f57115(@nospecialize(::Any)) = error("unimplemented") + function g57115(@nospecialize(x)) + if @noinline rand(Bool) + # Add an 'invoke' edge from 'foo' to 'bar' + Core.invoke(f57115, Tuple{Any}, x) + else + # ... and also an identical 'call' edge + @noinline f57115(x) + end + end # Adding new specializations should not invalidate `invoke`s function getlast(itr) @@ -1110,6 +1125,8 @@ precompile_test_harness("invoke") do dir """ module $CallerModule using $InvokeModule + import $InvokeModule: f57115, g57115 + # involving external modules callf(x) = f(x) callg(x) = x < 5 ? g(x) : invoke(g, Tuple{Real}, x) @@ -1130,6 +1147,8 @@ precompile_test_harness("invoke") do dir # Issue #44320 f44320(::Real) = 3 + # Issue #57115 + f57115(::Int) = 1 call_getlast(x) = getlast(x) @@ -1150,6 +1169,7 @@ precompile_test_harness("invoke") do dir @noinline internalnc(3) @noinline call_getlast([1,2,3]) end + precompile(g57115, (Any,)) # Now that we've precompiled, invalidate with a new method that overrides the `invoke` dispatch $InvokeModule.h(x::Integer) = -1 @@ -1217,6 +1237,30 @@ precompile_test_harness("invoke") do dir m = only(methods(M.g44320)) @test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt) + m = only(methods(M.g57115)) + mi = m.specializations::Core.MethodInstance + + f_m = get_method_for_type(M.f57115, Any) + f_mi = f_m.specializations::Core.MethodInstance + + # Make sure that f57115(::Any) has a 'call' backedge to 'g57115' + has_f_call_backedge = false + i = 1 + while i ≤ length(f_mi.backedges) + if f_mi.backedges[i] isa DataType + # invoke edge - skip + i += 2 + else + caller = f_mi.backedges[i]::Core.CodeInstance + if caller.def === mi + has_f_call_backedge = true + break + end + i += 1 + end + end + @test has_f_call_backedge + m = which(MI.getlast, (Any,)) @test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt) diff --git a/test/runtests.jl b/test/runtests.jl index fd0326d48ee6c..f0c5e1b94c376 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -112,7 +112,7 @@ cd(@__DIR__) do @everywhere include("testdefs.jl") if use_revise - Base.invokelatest(revise_trackall) + @invokelatest revise_trackall() Distributed.remotecall_eval(Main, workers(), revise_init_expr) end @@ -250,7 +250,7 @@ cd(@__DIR__) do wrkr = p before = time() resp, duration = try - r = remotecall_fetch(runtests, wrkr, test, test_path(test); seed=seed) + r = remotecall_fetch(@Base.world(runtests, ∞), wrkr, test, test_path(test); seed=seed) r, time() - before catch e isa(e, InterruptException) && return @@ -310,7 +310,7 @@ cd(@__DIR__) do t == "SharedArrays" && (isolate = false) before = time() resp, duration = try - r = Base.invokelatest(runtests, t, test_path(t), isolate, seed=seed) # runtests is defined by the include above + r = @invokelatest runtests(t, test_path(t), isolate, seed=seed) # runtests is defined by the include above r, time() - before catch e isa(e, InterruptException) && rethrow() diff --git a/test/syntax.jl b/test/syntax.jl index bdf9579b134eb..aaeeea7aec161 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3303,6 +3303,7 @@ const typeof = error end let ex = :(const $(esc(:x)) = 1; (::typeof($(esc(:foo43993))))() = $(esc(:x))) Core.eval(M43993, Expr(:var"hygienic-scope", ex, Core)) + @Core.latestworld @test M43993.x === 1 @test invokelatest(M43993.foo43993) === 1 end diff --git a/test/worlds.jl b/test/worlds.jl index 268a6664571fb..8bc96f8303aef 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -115,14 +115,14 @@ wc265_41332a = Task(tls_world_age) global wc265_41332d = Task(tls_world_age) nothing end)() -@test wc265 + 2 == get_world_counter() == tls_world_age() +@test wc265 + 3 == get_world_counter() == tls_world_age() schedule(wc265_41332a) schedule(wc265_41332b) schedule(wc265_41332c) schedule(wc265_41332d) @test wc265 == fetch(wc265_41332a) @test wc265 + 1 == fetch(wc265_41332b) -@test wc265 + 2 == fetch(wc265_41332c) +@test wc265 + 3 == fetch(wc265_41332c) @test wc265 + 1 == fetch(wc265_41332d) chnls, tasks = Base.channeled_tasks(2, wfunc) t265 = tasks[1]