diff --git a/Compiler/src/ssair/passes.jl b/Compiler/src/ssair/passes.jl index 1db3b84afd882..97f8f14b39f50 100644 --- a/Compiler/src/ssair/passes.jl +++ b/Compiler/src/ssair/passes.jl @@ -872,14 +872,15 @@ function perform_lifting!(compact::IncrementalCompact, return Pair{Any, PhiNest}(stmt_val, PhiNest(visited_philikes, lifted_philikes, lifted_leaves, reverse_mapping, walker_callback)) end -function lift_apply_args!(compact::IncrementalCompact, idx::Int, stmt::Expr, 𝕃ₒ::AbstractLattice) - # Handle _apply_iterate calls: convert arguments to use `Core.svec`. The behavior of Core.svec (with boxing) better matches the ABI of codegen. +# Handle _apply_iterate calls: convert arguments to use `Core.svec`. +# The behavior of `Core.svec` (with boxing) better matches the ABI of codegen. +function lift_apply_args!(compact::IncrementalCompact, idx::Int, stmt::Expr) compact[idx] = nothing - for i in 4:length(stmt.args) # Skip iterate function, f, and first iterator + for i in 4:length(stmt.args) # Skip `_apply_iterate`, `iterate`, and the function arg = stmt.args[i] - arg_type = argextype(arg, compact) - svec_args = nothing + arg_type = widenconst(argextype(arg, compact)) if isa(arg_type, DataType) && arg_type.name === Tuple.name + svec_args = nothing if isa(arg, SSAValue) arg_stmt = compact[arg][:stmt] if is_known_call(arg_stmt, Core.tuple, compact) @@ -900,15 +901,14 @@ function lift_apply_args!(compact::IncrementalCompact, idx::Int, stmt::Expr, end end end - end - # Create Core.svec call if we have arguments - if svec_args !== nothing - svec_args[1] = GlobalRef(Core, :svec) - new_svec_call = Expr(:call) - new_svec_call.args = svec_args - inst = compact[SSAValue(idx)] - new_svec_ssa = insert_node!(compact, SSAValue(idx), NewInstruction(new_svec_call, SimpleVector, NoCallInfo(), inst[:line], inst[:flag])) - stmt.args[i] = new_svec_ssa + if svec_args !== nothing + svec_args[1] = GlobalRef(Core, :svec) + new_svec_call = Expr(:call) + new_svec_call.args = svec_args + inst = compact[SSAValue(idx)] + new_svec_ssa = insert_node!(compact, SSAValue(idx), NewInstruction(new_svec_call, SimpleVector, NoCallInfo(), inst[:line], inst[:flag])) + stmt.args[i] = new_svec_ssa + end end end compact[idx] = stmt @@ -1420,7 +1420,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) refine_new_effects!(𝕃ₒ, compact, idx, stmt) elseif is_known_call(stmt, Core._apply_iterate, compact) length(stmt.args) >= 4 || continue - lift_apply_args!(compact, idx, stmt, 𝕃ₒ) + lift_apply_args!(compact, idx, stmt) end continue end diff --git a/Compiler/test/irpasses.jl b/Compiler/test/irpasses.jl index 4593aa3223902..77a66ec6435fa 100644 --- a/Compiler/test/irpasses.jl +++ b/Compiler/test/irpasses.jl @@ -2045,3 +2045,17 @@ let src = code_typed1(()) do end @test count(iscall((src, setfield!)), src.code) == 1 end + +# JuliaLang/julia #59548 +# Rewrite `Core._apply_iterate` to use `Core.svec` instead of `tuple` to better match +# the codegen ABI +let src = code_typed1((Vector{Any},)) do xs + println(stdout, xs...) + end + @test count(iscall((src, Core.svec)), src.code) == 1 +end +let src = code_typed1((Vector{Any},)) do xs + println(stdout, 1, xs...) # convert tuples represented by `PartialStruct` + end + @test count(iscall((src, Core.svec)), src.code) == 1 +end diff --git a/base/client.jl b/base/client.jl index 34c0fb828e978..5aeca3f09fbcc 100644 --- a/base/client.jl +++ b/base/client.jl @@ -267,10 +267,6 @@ function exec_options(opts) interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY) is_interactive::Bool |= interactiveinput - # load terminfo in for styled printing - term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") - global current_terminfo = load_terminfo(term_env) - # load ~/.julia/config/startup.jl file if startup try diff --git a/base/lock.jl b/base/lock.jl index 2da4d47c3734b..e74da826900a6 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -767,7 +767,7 @@ end # share a lock/condition, since we just need it briefly, so some contention is okay -const PerThreadLock = ThreadSynchronizer() +const PerThreadLock = Threads.SpinLock() """ OncePerThread{T}(init::Function)() -> T @@ -871,7 +871,15 @@ OncePerThread(initializer) = OncePerThread{Base.promote_op(initializer), typeof( state = @atomic :monotonic ss[tid] while state == PerStateConcurrent # lost race, wait for notification this is done running elsewhere - wait(PerThreadLock) # wait for initializer to finish without releasing this thread + # without releasing this thread + unlock(PerThreadLock) + while state == PerStateConcurrent + # spin loop until ready + ss = @atomic :acquire once.ss + state = @atomic :monotonic ss[tid] + GC.safepoint() + end + lock(PerThreadLock) ss = @atomic :monotonic once.ss state = @atomic :monotonic ss[tid] end @@ -885,7 +893,6 @@ OncePerThread(initializer) = OncePerThread{Base.promote_op(initializer), typeof( lock(PerThreadLock) ss = @atomic :monotonic once.ss @atomic :release ss[tid] = PerStateErrored - notify(PerThreadLock) rethrow() end # store result and notify waiters @@ -894,7 +901,6 @@ OncePerThread(initializer) = OncePerThread{Base.promote_op(initializer), typeof( @atomic :release xs[tid] = result ss = @atomic :monotonic once.ss @atomic :release ss[tid] = PerStateHasrun - notify(PerThreadLock) elseif state == PerStateErrored error("OncePerThread initializer failed previously") elseif state != PerStateHasrun diff --git a/base/reduce.jl b/base/reduce.jl index 2a6a268f2e6c1..9474f26a174ee 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -212,7 +212,7 @@ Like [`mapreduce`](@ref), but with guaranteed right associativity, as in [`foldr provided, the keyword argument `init` will be used exactly once. In general, it will be necessary to provide `init` to work with empty collections. """ -mapfoldr(f, op, itr; init=_InitialValue()) = mapfoldr_impl(f, op, init, itr) +mapfoldr(f::F, op::F2, itr; init=_InitialValue()) where {F,F2} = mapfoldr_impl(f, op, init, itr) """ @@ -231,7 +231,7 @@ julia> foldr(=>, 1:4; init=0) 1 => (2 => (3 => (4 => 0))) ``` """ -foldr(op, itr; kw...) = mapfoldr(identity, op, itr; kw...) +foldr(op::F, itr; kw...) where {F} = mapfoldr(identity, op, itr; kw...) ## reduce & mapreduce diff --git a/base/strings/annotated_io.jl b/base/strings/annotated_io.jl index 9698fd5909b68..efa84b6b99304 100644 --- a/base/strings/annotated_io.jl +++ b/base/strings/annotated_io.jl @@ -200,6 +200,8 @@ function _insert_annotations!(io::AnnotatedIOBuffer, annotations::Vector{RegionA end end +function printstyled end + # NOTE: This is an interim solution to the invalidations caused # by the split styled display implementation. This should be # replaced by a more robust solution (such as a consolidation of @@ -250,6 +252,14 @@ Base.print(io::AnnotatedIOBuffer, s::Union{<:AnnotatedString, SubString{<:Annota Base.print(io::AnnotatedIOBuffer, c::AnnotatedChar) = (write(io, c); nothing) +styled_print(io::AnnotatedIOBuffer, msg::Any, kwargs::Any) = print(io, msg...) + +styled_print_(io::AnnotatedIOBuffer, @nospecialize(msg), @nospecialize(kwargs)) = + invoke_in_world(tls_world_age(), styled_print, io, msg, kwargs)::Nothing + +Base.printstyled(io::AnnotatedIOBuffer, msg...; kwargs...) = + styled_print_(io, msg, kwargs) + # Escape Base.escape_string(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}, diff --git a/base/terminfo.jl b/base/terminfo.jl index 8ea8387077d36..5439a5d9868a3 100644 --- a/base/terminfo.jl +++ b/base/terminfo.jl @@ -303,16 +303,24 @@ end """ The terminfo of the current terminal. """ -current_terminfo::TermInfo = TermInfo() +const current_terminfo = OncePerProcess{TermInfo}() do + term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") + terminfo = load_terminfo(term_env) + # Ensure setaf is set for xterm terminals + if !haskey(terminfo, :setaf) && startswith(term_env, "xterm") + # For xterm-like terminals without setaf, add a reasonable default + terminfo.strings[:setaf] = "\e[3%p1%dm" + end + return terminfo +end # Legacy/TTY methods and the `:color` parameter if Sys.iswindows() - ttyhascolor(term_type = nothing) = true + ttyhascolor() = true else - function ttyhascolor(term_type = get(ENV, "TERM", "")) - startswith(term_type, "xterm") || - haskey(current_terminfo, :setaf) + function ttyhascolor() + haskey(current_terminfo(), :setaf) end end @@ -352,9 +360,9 @@ Multiple conditions are taken as signifying truecolor support, specifically any function ttyhastruecolor() # Lasciate ogne speranza, voi ch'intrate get(ENV, "COLORTERM", "") ∈ ("truecolor", "24bit") || - get(current_terminfo, :RGB, false) || get(current_terminfo, :Tc, false) || - (haskey(current_terminfo, :setrgbf) && haskey(current_terminfo, :setrgbb)) || - @static if Sys.isunix() get(current_terminfo, :colors, 0) > 256 else false end || + get(current_terminfo(), :RGB, false) || get(current_terminfo(), :Tc, false) || + (haskey(current_terminfo(), :setrgbf) && haskey(current_terminfo(), :setrgbb)) || + @static if Sys.isunix() get(current_terminfo(), :colors, 0) > 256 else false end || (Sys.iswindows() && Sys.windows_version() ≥ v"10.0.14931") || # See something(tryparse(Int, get(ENV, "VTE_VERSION", "")), 0) >= 3600 || # Per GNOME bug #685759 haskey(ENV, "XTERM_VERSION") || diff --git a/base/timing.jl b/base/timing.jl index 9e3a4cf128413..e937d396a52a2 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -472,41 +472,93 @@ function gc_bytes() b[] end -function allocated(f, args::Vararg{Any,N}) where {N} +@constprop :none function allocated(f, args::Vararg{Any,N}) where {N} b0 = Ref{Int64}(0) b1 = Ref{Int64}(0) Base.gc_bytes(b0) - f(args...) + @noinline f(args...) Base.gc_bytes(b1) return b1[] - b0[] end only(methods(allocated)).called = 0xff -function allocations(f, args::Vararg{Any,N}) where {N} +@constprop :none function allocations(f, args::Vararg{Any,N}) where {N} stats = Base.gc_num() - f(args...) + @noinline f(args...) diff = Base.GC_Diff(Base.gc_num(), stats) return Base.gc_alloc_count(diff) end only(methods(allocations)).called = 0xff function is_simply_call(@nospecialize ex) + is_simple_atom(a) = a isa QuoteNode || a isa Symbol || is_self_quoting(a) Meta.isexpr(ex, :call) || return false for a in ex.args - a isa QuoteNode && continue - a isa Symbol && continue - Base.is_self_quoting(a) && continue + is_simple_atom(a) && continue + Meta.isexpr(a, :..., 1) && is_simple_atom(a.args[1]) && continue return false end return true end +function _gen_allocation_measurer(ex, fname::Symbol) + if isexpr(ex, :call) + if !is_simply_call(ex) + ex = :((() -> $ex)()) + end + pushfirst!(ex.args, GlobalRef(Base, fname)) + return quote + Experimental.@force_compile + $(esc(ex)) + end + elseif fname === :allocated + # v1.11-compatible implementation + return quote + Experimental.@force_compile + local b0 = Ref{Int64}(0) + local b1 = Ref{Int64}(0) + gc_bytes(b0) + $(esc(ex)) + gc_bytes(b1) + b1[] - b0[] + end + else + @assert fname === :allocations + return quote + Experimental.@force_compile + # Note this value is unused, but without it `allocated` and `allocations` + # are sufficiently different that the compiler can remove allocations here + # that it cannot remove there, giving inconsistent numbers. + local b1 = Ref{Int64}(0) + local stats = Base.gc_num() + $(esc(ex)) + local diff = Base.GC_Diff(Base.gc_num(), stats) + gc_bytes(b1) + Base.gc_alloc_count(diff) + end + end +end + """ @allocated A macro to evaluate an expression, discarding the resulting value, instead returning the total number of bytes allocated during evaluation of the expression. +If the expression is a function call, an effort is made to measure only allocations from +the argument expressions and during the function, excluding any overhead from calling it +and not performing constant propagation with the provided argument values. If you want to +include those effects, i.e. measuring the call site as well, use the syntax +`@allocated (()->f(1))()`. + +It is recommended to measure function calls with only simple argument expressions, e.g. +`x = []; @allocated f(x)` instead of `@allocated f([])` to clarify that only `f` is +being measured. + +For more complex expressions, the code is simply run in place and therefore may see +allocations due to the surrounding context. For example it is possible for +`@allocated f(1)` and `@allocated x = f(1)` to give different results. + See also [`@allocations`](@ref), [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref), and [`@elapsed`](@ref). @@ -516,11 +568,7 @@ julia> @allocated rand(10^6) ``` """ macro allocated(ex) - if !is_simply_call(ex) - ex = :((() -> $ex)()) - end - pushfirst!(ex.args, GlobalRef(Base, :allocated)) - return esc(ex) + _gen_allocation_measurer(ex, :allocated) end """ @@ -541,11 +589,7 @@ julia> @allocations rand(10^6) This macro was added in Julia 1.9. """ macro allocations(ex) - if !is_simply_call(ex) - ex = :((() -> $ex)()) - end - pushfirst!(ex.args, GlobalRef(Base, :allocations)) - return esc(ex) + _gen_allocation_measurer(ex, :allocations) end diff --git a/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/md5 b/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/md5 new file mode 100644 index 0000000000000..9b1701abea5dc --- /dev/null +++ b/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/md5 @@ -0,0 +1 @@ +93162fc479ba1762028ef917176f45e0 diff --git a/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/sha512 b/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/sha512 new file mode 100644 index 0000000000000..721f154ad5366 --- /dev/null +++ b/deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/sha512 @@ -0,0 +1 @@ +d6c421048e52d5cf32848cf7a16db1ac269f2c553672b0f1126230a7f5954adb6f2883982efe747bd91fa8135071e1b2f717a12ceb1631c1c7effbf6cece8f4c diff --git a/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/md5 b/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/md5 deleted file mode 100644 index b8651ee8f2d46..0000000000000 --- a/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -f41f7e4247fddb5531dea530bee83024 diff --git a/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/sha512 b/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/sha512 deleted file mode 100644 index 6523086dbd0a1..0000000000000 --- a/deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -d2596e62f75841be0da013c8c41708577ae2527d3564d95081385d77fc00da514a46f25bbb42304b76127890018358cb191c80c18ba57818c2fde8ebedde260b diff --git a/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/md5 b/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/md5 new file mode 100644 index 0000000000000..ab3e4ebfe9f25 --- /dev/null +++ b/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/md5 @@ -0,0 +1 @@ +eadaa92895c8d4d33eb601165ef765d5 diff --git a/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/sha512 b/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/sha512 new file mode 100644 index 0000000000000..7a250e67b00d3 --- /dev/null +++ b/deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/sha512 @@ -0,0 +1 @@ +bb37377b360eca1a32c78b1d11b83c7e918a8ddb9df79388694b6f415dc5d5cf6182df7437869b3970011e5dcda4a3f821b58498bfa6fd7df697fcd51383ca12 diff --git a/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/md5 b/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/md5 deleted file mode 100644 index 0cd5ebfb0fd87..0000000000000 --- a/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -9f4376c0de171f481c153442dee250a4 diff --git a/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/sha512 b/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/sha512 deleted file mode 100644 index 207b0685cc2a5..0000000000000 --- a/deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -ee756aa32d95849f728f7cfae8ba9cfce8ad51701cee6430e547f6b80b1f420e78d7d74471afc25ffc8cf6699cd5ae4e63cf38c32f3e8279179630074fda1830 diff --git a/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/md5 b/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/md5 deleted file mode 100644 index 46d5cacf788df..0000000000000 --- a/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -1cb6007a66d3f74cbe5b27ee449aa9c8 diff --git a/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/sha512 b/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/sha512 deleted file mode 100644 index 724b2d311c123..0000000000000 --- a/deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -1fa95646fdf4cc7ea282bd355fded9464e7572792912942ea1c45f6ed126eead2333fdeed92e7db3efbcd6c3a171a04e5c9562dab2685bb39947136284ae1da3 diff --git a/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/md5 b/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/md5 new file mode 100644 index 0000000000000..e58ba4519f3ff --- /dev/null +++ b/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/md5 @@ -0,0 +1 @@ +ad2e6ba06c98990865f808b26b8f148c diff --git a/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/sha512 b/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/sha512 new file mode 100644 index 0000000000000..8f7b0e2111bce --- /dev/null +++ b/deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/sha512 @@ -0,0 +1 @@ +24e530c095f7838380adeb6f45349cf776df524a2fc721eb8b11411d25bc132a58c1048a89d630ba1ee66bf9a52cce9a0fbe2b4a76c33b11160c00ecb7a919a1 diff --git a/src/gf.c b/src/gf.c index 23b863294b45b..b13830c1e1ba9 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1482,10 +1482,14 @@ static int concretesig_equal(jl_value_t *tt, jl_value_t *simplesig) JL_NOTSAFEPO return 1; } +// if available, returns a TypeMapEntry in the "leafcache" that matches `tt` (by type-equality) and is valid during `world` static inline jl_typemap_entry_t *lookup_leafcache(jl_genericmemory_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT { jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_eqtable_get(leafcache, (jl_value_t*)tt, NULL); if (entry) { + // search tail of the linked-list (including the returned entry) for an entry intersecting world + // + // n.b. this entire chain is type-equal to tt (by construction), so it is unnecessary to call `tt<:entry->sig` do { if (jl_atomic_load_relaxed(&entry->min_world) <= world && world <= jl_atomic_load_relaxed(&entry->max_world)) { if (entry->simplesig == (void*)jl_nothing || concretesig_equal(tt, (jl_value_t*)entry->simplesig)) @@ -4389,21 +4393,31 @@ static jl_method_match_t *make_method_match(jl_tupletype_t *spec_types, jl_svec_ return match; } +// callback for typemap_visitor +// +// This will exit the search early (by returning 0 / false) if the match limit is proven to be +// exceeded early. This is only best-effort, since specificity means that many matched methods +// may be sorted and removed in the output processing for ml_matches and therefore we can only +// conservatively under-approximate the matches during the search. static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure0) { struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match); if (closure->intersections == 0 && !closure0->issubty) return 1; + + // First, check the world range of the typemap entry to ensure that it intersects + // the query world. If it does not, narrow the result world range to guarantee + // excluding it from the results is valid for the full span. size_t min_world = jl_atomic_load_relaxed(&ml->min_world); size_t max_world = jl_atomic_load_relaxed(&ml->max_world); if (closure->world < min_world) { - // ignore method table entries that are part of a later world + // exclude method table entries that are part of a later world if (closure->match.max_valid >= min_world) closure->match.max_valid = min_world - 1; return 1; } else if (closure->world > max_world) { - // ignore method table entries that have been replaced in the current world + // exclude method table entries that have been replaced in the current world if (closure->match.min_valid <= max_world) closure->match.min_valid = max_world + 1; return 1; @@ -4601,21 +4615,47 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, else va = NULL; } - struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0, - /* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid, - /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, - intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any, - /* .matc = */ NULL}; + struct ml_matches_env env = { + /* match */ { + /* inputs */ + /* fptr / callback */ ml_matches_visitor, + /* sig */ (jl_value_t*)type, + /* vararg type / tparam0 */ va, + + /* temporaries */ + /* .search_slurp = */ 0, + + /* outputs */ + /* .min_valid = */ *min_valid, + /* .max_valid = */ *max_valid, + /* .ti = */ NULL, + /* .env = */ jl_emptysvec, + /* .issubty = */ 0 + }, + /* inputs */ + intersections, + world, + lim, + include_ambiguous, + + /* outputs */ + /* .t = */ jl_an_empty_vec_any, + + /* temporaries */ + /* .matc = */ NULL + }; struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec}; jl_value_t *isect2 = NULL; JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2); if (mc) { - // check the leaf cache if this type can be in there + // first check the leaf cache if the type might have been put in there if (((jl_datatype_t*)unw)->isdispatchtuple) { jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mc->leafcache); jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world); if (entry) { + // leafcache found a match, construct the MethodMatch by computing the effective + // types + sparams and the world bounds jl_method_instance_t *mi = entry->func.linfo; jl_method_t *meth = mi->def.method; if (!jl_is_unionall(meth->sig)) { @@ -4644,10 +4684,13 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, return env.t; } } + // then check the full cache if it seems profitable if (((jl_datatype_t*)unw)->isdispatchtuple) { jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mc->cache), &search, jl_cachearg_offset(), /*subtype*/1); if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) { + // full cache found a match, construct the MethodMatch by computing the effective + // types + sparams and the world bounds jl_method_instance_t *mi = entry->func.linfo; jl_method_t *meth = mi->def.method; size_t min_world = jl_atomic_load_relaxed(&entry->min_world); @@ -4679,7 +4722,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, // then scan everything if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), 0, &env.match) && env.t == jl_an_empty_vec_any) { JL_GC_POP(); - // if we return early without returning methods, set only the min/max valid collected from matching + // if we return early without returning methods, lim was proven to be exceeded + // during the search set only the min/max valid collected from matching *min_valid = env.match.min_valid; *max_valid = env.match.max_valid; return jl_nothing; @@ -4689,12 +4733,19 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, *max_valid = env.match.max_valid; // done with many of these values now env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL; + + // all intersecting methods have been collected now. the remaining work is to sort + // these and apply specificity to determine a list of dispatch-possible call targets size_t i, j, len = jl_array_nrows(env.t); + + // the 'minmax' method is a method that (1) fully-covers the queried type, and (2) is + // more-specific than any other fully-covering method (but if !all_subtypes, there are + // non-fully-covering methods to which it is _likely_ not more specific) jl_method_match_t *minmax = NULL; int any_subtypes = 0; if (len > 1) { - // first try to pre-process the results to find the most specific - // result that fully covers the input, since we can do this in O(n^2) + // first try to pre-process the results to find the most specific option + // among the fully-covering methods, since we can do this in O(n^2) // time, and the rest is O(n^3) // - first find a candidate for the best of these method results for (i = 0; i < len; i++) { @@ -4719,8 +4770,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, } } } - // - it may even dominate some choices that are not subtypes! - // move those into the subtype group, where we're filter them out shortly after + // - it may even dominate (be more specific than) some choices that are not fully-covering! + // move those into the subtype group, where we'll filter them out shortly after // (potentially avoiding reporting these as an ambiguity, and // potentially allowing us to hit the next fast path) // - we could always check here if *any* FULLY_COVERS method is @@ -4733,6 +4784,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, jl_method_t *minmaxm = NULL; if (minmax != NULL) minmaxm = minmax->method; + // scan through all the non-fully-matching methods and count them as "fully-covering" (ish) + // (i.e. in the 'subtype' group) if `minmax` is more-specific for (i = 0; i < len; i++) { jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); if (matc->fully_covers != FULLY_COVERS) { @@ -4753,16 +4806,21 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc, // we've already processed all of the possible outputs if (all_subtypes) { if (minmax == NULL) { + // all intersecting methods are fully-covering, but there is no unique most-specific method if (!include_ambiguous) { + // there no unambiguous choice of method len = 0; env.t = jl_an_empty_vec_any; } else if (lim == 1) { + // we'd have to return >1 method due to the ambiguity, so bail early JL_GC_POP(); return jl_nothing; } } else { + // `minmax` is more-specific than all other matches and is fully-covering + // we can return it as our only result jl_array_ptr_set(env.t, 0, minmax); jl_array_del_end((jl_array_t*)env.t, len - 1); len = 1; diff --git a/src/julia.h b/src/julia.h index d5ffa9575a69a..e8322119299a3 100644 --- a/src/julia.h +++ b/src/julia.h @@ -883,14 +883,20 @@ typedef struct _jl_typemap_level_t { typedef struct _jl_methcache_t { JL_DATA_TYPE + // hash map from dispatchtuple type to a linked-list of TypeMapEntry + // entry.sig == type for all entries in the linked-list _Atomic(jl_genericmemory_t*) leafcache; + + // cache for querying everything else (anything that didn't seem profitable to put into leafcache) _Atomic(jl_typemap_t*) cache; + jl_mutex_t writelock; } jl_methcache_t; // contains global MethodTable typedef struct _jl_methtable_t { JL_DATA_TYPE + // full set of entries _Atomic(jl_typemap_t*) defs; jl_methcache_t *cache; jl_sym_t *name; // sometimes used for debug printing diff --git a/src/typemap.c b/src/typemap.c index 8e67428391aef..f4e0520291816 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -1331,6 +1331,9 @@ static void jl_typemap_list_insert_( jl_typemap_entry_t *newrec) { jl_typemap_entry_t *l = jl_atomic_load_relaxed(pml); + + // Pick the first intersection point that guarantees that the list ordering + // will be (leaf sigs..., simple sigs..., other sigs...) while ((jl_value_t*)l != jl_nothing) { if (newrec->isleafsig || !l->isleafsig) if (newrec->issimplesig || !l->issimplesig) @@ -1339,6 +1342,7 @@ static void jl_typemap_list_insert_( parent = (jl_value_t*)l; l = jl_atomic_load_relaxed(&l->next); } + jl_atomic_store_relaxed(&newrec->next, l); jl_gc_wb(newrec, l); jl_atomic_store_release(pml, newrec); @@ -1356,6 +1360,7 @@ static void jl_typemap_insert_generic( jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); return; } + if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) { assert(!doublesplit); jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs); diff --git a/stdlib/LinearAlgebra.version b/stdlib/LinearAlgebra.version index 6ee231d7dd649..b5383b0749ae0 100644 --- a/stdlib/LinearAlgebra.version +++ b/stdlib/LinearAlgebra.version @@ -1,4 +1,4 @@ LINEARALGEBRA_BRANCH = release-1.12 -LINEARALGEBRA_SHA1 = b5a8bb07059f0d499c493c6db01980b060836f5a +LINEARALGEBRA_SHA1 = 24f5e21cf3a560ca560c5a1759ff21ba68382ebd LINEARALGEBRA_GIT_URL := https://github.com/JuliaLang/LinearAlgebra.jl.git LINEARALGEBRA_TAR_URL = https://api.github.com/repos/JuliaLang/LinearAlgebra.jl/tarball/$1 diff --git a/stdlib/Makefile b/stdlib/Makefile index 3975f24b7ae3b..bf14c7ee834b9 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -15,6 +15,8 @@ include $(JULIAHOME)/deps/*.version VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) DIRS := $(build_datarootdir)/julia/stdlib/$(VERSDIR) $(build_prefix)/manifest/$(VERSDIR) +LIBDIR := $(build_datarootdir)/lib/julia + $(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) JLLS = DSFMT GMP CURL LIBGIT2 LLVM LIBSSH2 LIBUV OPENSSL MPFR NGHTTP2 \ @@ -60,8 +62,19 @@ $(foreach module, $(STDLIBS), $(eval $(call symlink_target,$$(JULIAHOME)/stdlib/ STDLIBS_LINK_TARGETS := $(addprefix $(build_datarootdir)/julia/stdlib/$(VERSDIR)/,$(STDLIBS)) +remove-gpl-libs: +ifeq ($(USE_GPL_LIBS),0) + @echo Removing GPL libs... + -rm -f $(LIBDIR)/libcholmod* + -rm -f $(LIBDIR)/libklu_cholmod* + -rm -f $(LIBDIR)/librbio* + -rm -f $(LIBDIR)/libspqr* + -rm -f $(LIBDIR)/libumfpack* +endif + getall get: $(addprefix get-, $(STDLIBS_EXT) $(JLL_NAMES)) -install: version-check $(addprefix install-, $(STDLIBS_EXT) $(JLL_NAMES)) $(STDLIBS_LINK_TARGETS) + +install: version-check $(addprefix install-, $(STDLIBS_EXT) $(JLL_NAMES)) $(STDLIBS_LINK_TARGETS) remove-gpl-libs version-check: $(addprefix version-check-, $(STDLIBS_EXT)) uninstall: $(addprefix uninstall-, $(STDLIBS_EXT)) extstdlibclean: diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 4e9de62c06d6f..b6d9a820d9a06 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = release-1.12 -SPARSEARRAYS_SHA1 = cdbad55530fba0c7aa27d4bcc64dde2204ff133f +SPARSEARRAYS_SHA1 = 5d674dc7bd90156cf8ecea4e143b69b5a5b7640d SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 diff --git a/stdlib/StyledStrings.version b/stdlib/StyledStrings.version index 55a4a08c17ea0..b77e1a8cd9f59 100644 --- a/stdlib/StyledStrings.version +++ b/stdlib/StyledStrings.version @@ -1,4 +1,4 @@ STYLEDSTRINGS_BRANCH = main -STYLEDSTRINGS_SHA1 = 3fe829fcf611b5fefaefb64df7e61f2ae82db117 +STYLEDSTRINGS_SHA1 = 68bf7b1f83f334391dc05fda34f48267e04e2bd0 STYLEDSTRINGS_GIT_URL := https://github.com/JuliaLang/StyledStrings.jl.git STYLEDSTRINGS_TAR_URL = https://api.github.com/repos/JuliaLang/StyledStrings.jl/tarball/$1 diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index a79395c88231e..1630bf7dde0fd 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -349,8 +349,9 @@ if bc_opt == bc_default m2 = Memory{Int}(undef,n) m1 === m2 end - no_alias_prove(1) - @test (@allocated no_alias_prove(5)) == 0 + no_alias_prove5() = no_alias_prove(5) + no_alias_prove5() + @test (@allocated no_alias_prove5()) == 0 end end diff --git a/test/misc.jl b/test/misc.jl index 60a0262473b91..8d6ee39715303 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1522,15 +1522,40 @@ end # issue #41656 run(`$(Base.julia_cmd()) -e 'isempty(x) = true'`) +function treshape59278(X::AbstractArray, n, m) + Y = reshape(X, n, m) + Y .= 1.0 + return X +end + +# a function that allocates iff no constprop +@inline maybealloc59278(n, _) = ntuple(i->rand(), n) + @testset "Base/timing.jl" begin @test Base.jit_total_bytes() >= 0 # sanity check `@allocations` returns what we expect in some very simple cases. - # These are inside functions because `@allocations` uses `Experimental.@force_compile` - # so can be affected by other code in the same scope. @test (() -> @allocations "a")() == 0 - @test (() -> @allocations "a" * "b")() == 0 # constant propagation + "a" * Base.inferencebarrier("b") @test (() -> @allocations "a" * Base.inferencebarrier("b"))() == 1 + # test that you can grab the value from @allocated + @allocated _x = 1+2 + @test _x === 3 + + n, m = 10, 20 + X = rand(n, m) + treshape59278(X, n, m) + # test that @allocated and @allocations are consistent about whether anything was + # allocated in a case where the compiler can sometimes remove an allocation + # https://github.com/JuliaLang/julia/issues/58634#issuecomment-2940840651 + @test ((@allocated treshape59278(X, n, m))==0) == ((@allocations treshape59278(X, n, m))==0) + # TODO: would be nice to have but not yet reliable + #@test ((@allocated begin treshape59278(X, n, m) end)==0) == ((@allocations begin treshape59278(X, n, m) end)==0) + + # test that all wrapped allocations are counted and constprop is not done + @test (@allocated @noinline maybealloc59278(10, [])) > (@allocated maybealloc59278(10, 0)) > 0 + # but if you wrap it in another function it can be constprop'd + @test (@allocated (()->maybealloc59278(10, []))()) == 0 _lock_conflicts, _nthreads = eval(Meta.parse(read(`$(Base.julia_cmd()) -tauto -E ' _lock_conflicts = @lock_conflicts begin