From 294a574e4caee1e8f77e1c4558897546d7f47f40 Mon Sep 17 00:00:00 2001 From: KristofferC Date: Mon, 22 Sep 2025 11:41:15 +0200 Subject: [PATCH 1/9] bump SparseArrays to latest 1.12 --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-5d674dc7bd90156cf8ecea4e143b69b5a5b7640d.tar.gz/sha512 delete mode 100644 deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-cdbad55530fba0c7aa27d4bcc64dde2204ff133f.tar.gz/sha512 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/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 From 64f2dcaa09f703650cc595accd193ce85b1c952a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:59:35 +0900 Subject: [PATCH 2/9] optimizations: improve `Core._apply_iterate` call conversion in #59548 (#59601) Further improves the implementation from JuliaLang/julia#59548. Specifically, uses `widenconst` to enable conversion of `tuple` calls that have become `PartialStruct`, and removes incorrect comments and unused arguments. Also adds some Julia-IR level tests. --- Compiler/src/ssair/passes.jl | 30 +++++++++++++++--------------- Compiler/test/irpasses.jl | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 15 deletions(-) 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 From 4e05f0030b927ae786240eb97728ca1bf2ee9747 Mon Sep 17 00:00:00 2001 From: Timothy Date: Tue, 23 Sep 2025 23:23:22 +0800 Subject: [PATCH 3/9] Apply an inference barrier to annot printstyled (#59596) In 6d78a4a we introduced an inference barrier to the various AnnotatedX/StyledStrings methods whose implementations are frustratingly split between Base and a Stdlib, to ease the pain of invalidations. As has recently been pointed out in the re-opened JuliaLang#57997, this missed a `printstyled` method. We address that here, together with a tweak to the StyledStrings library. Reported-by: Neven Sajko (cherry picked from commit 406d37b79df0550e63a6ace26304d37b6afeca0d) --- base/strings/annotated_io.jl | 10 ++++++++++ .../md5 | 1 + .../sha512 | 1 + stdlib/StyledStrings.version | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/md5 create mode 100644 deps/checksums/StyledStrings-68bf7b1f83f334391dc05fda34f48267e04e2bd0.tar.gz/sha512 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/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/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 From 4fd544f82c1aff3faffa1c295dbff866e140214b Mon Sep 17 00:00:00 2001 From: KristofferC Date: Tue, 23 Sep 2025 17:55:46 +0200 Subject: [PATCH 4/9] bump LinearAlgebra to latest 1.12 --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/LinearAlgebra.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/md5 create mode 100644 deps/checksums/LinearAlgebra-24f5e21cf3a560ca560c5a1759ff21ba68382ebd.tar.gz/sha512 delete mode 100644 deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/md5 delete mode 100644 deps/checksums/LinearAlgebra-b5a8bb07059f0d499c493c6db01980b060836f5a.tar.gz/sha512 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/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 From 90d0063f41799cf2fe595024a768b9b2f1782b0e Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Mon, 22 Sep 2025 14:33:31 -0400 Subject: [PATCH 5/9] Remove GPL libraries from the Julia build for binary-dist target (#59627) Currently we support removing GPL dependencies in the full source build. This will also remove the GPL dependencies from the binary-dist target when built with JLLs. I almost feel like it would be simpler to have a new SuiteSparse_NOGPL_jll package. Then in the default build, things stay as they are. In the no gpl build use the new JLL. In the no GPL build, if someone then tries to use a GPL SuiteSparse library, a warning can be printed asking them to get a different build of Julia. @DilumAluthge @andreasnoack @giordano Thoughts? Co-authored-by: Viral B. Shah (cherry picked from commit 441ebf958477aad68158b7d600c30278b2644d8c) --- stdlib/Makefile | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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: From e6044adbefae03063a088ed499108be304d5bc70 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:12:35 -0400 Subject: [PATCH 6/9] gf.c: Add some comments to `ml_matches` (NFC) (#58304) Adding notes as I go through this code in detail. Co-authored-by: Jameson Nash (cherry picked from commit f2cc6b0765f1c78f9071498c028a90506257e4e5) --- src/gf.c | 84 +++++++++++++++++++++++++++++++++++++++++++-------- src/julia.h | 6 ++++ src/typemap.c | 5 +++ 3 files changed, 82 insertions(+), 13 deletions(-) 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); From 77ca1455e4f8e3c5a4ea5b0f313a17e98c248d78 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 24 Sep 2025 03:20:57 -0400 Subject: [PATCH 7/9] avoid startup deadlock in scheduler (#59583) (cherry picked from commit 61fdceaef8a4f3b2afa3bedb101b71ae6050a7be) --- base/lock.jl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 From 644f40ec6e5c4de73619812ae7064abe2e05edae Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 14 Aug 2025 15:05:11 -0400 Subject: [PATCH 8/9] make `@allocated x = f(...)` work as before Also add `@constprop :none` and `@noinline` to prevent the measuring code from interfering with the subject code. Fixes #58780 (cherry picked from commit 23633c2c7c56fe52d1b93420dfac29d3961cc6cb) (cherry picked from commit 084dab19178446fd5f78bd61b8f3a6aed95ffae3) --- base/reduce.jl | 4 +-- base/timing.jl | 78 +++++++++++++++++++++++++++++++--------- test/boundscheck_exec.jl | 5 +-- test/misc.jl | 31 ++++++++++++++-- 4 files changed, 94 insertions(+), 24 deletions(-) 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/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/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 From ad32b08ab8de8dca51eac30ef715022f80f7380b Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 10 Jul 2025 00:45:06 -0400 Subject: [PATCH 9/9] Make current_terminfo a OncePerProcess (#58854) There seems to be no reason to always load this unconditionally - especially since it's in the critical startup path. If we never print colored output or our IO is not a TTY, we don't need to load this at all. While we're at it, remove the `term_type` argument to `ttyhascolor`, which didn't work as advertised anyway, since it still looked at the current_terminfo. If clients want to do a full TermInfo check, they can do that explicitly. (Written by Claude Code) (cherry picked from commit 72e2c45554dd6e7f55daea4a5f234b246f6e93e8) --- base/client.jl | 4 ---- base/terminfo.jl | 24 ++++++++++++------- .../md5 | 1 - .../sha512 | 1 - 4 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/md5 delete mode 100644 deps/checksums/StyledStrings-3fe829fcf611b5fefaefb64df7e61f2ae82db117.tar.gz/sha512 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/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/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