From 9738bc79854df1f0de41745d06edf2a9ba5ad8a6 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 19 Aug 2024 19:29:44 +0530 Subject: [PATCH 01/94] Fix tr for Symmetric/Hermitian block matrices (#55522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since `Symmetric` and `Hermitian` symmetrize the diagonal elements of the parent, we can't forward `tr` to the parent unless it is already symmetric. This limits the existing `tr` methods to matrices of `Number`s, which is the common use-case. `tr` for `Symmetric` block matrices would now use the fallback implementation that explicitly computes the `diag`. This resolves the following discrepancy: ```julia julia> S = Symmetric(fill([1 2; 3 4], 3, 3)) 3×3 Symmetric{AbstractMatrix, Matrix{Matrix{Int64}}}: [1 2; 2 4] [1 2; 3 4] [1 2; 3 4] [1 3; 2 4] [1 2; 2 4] [1 2; 3 4] [1 3; 2 4] [1 3; 2 4] [1 2; 2 4] julia> tr(S) 2×2 Matrix{Int64}: 3 6 9 12 julia> sum(diag(S)) 2×2 Symmetric{Int64, Matrix{Int64}}: 3 6 6 12 ``` --- stdlib/LinearAlgebra/src/symmetric.jl | 4 ++-- stdlib/LinearAlgebra/test/symmetric.jl | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index 55630595f6fb2..c336785792588 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -449,8 +449,8 @@ Base.copy(A::Adjoint{<:Any,<:Symmetric}) = Base.copy(A::Transpose{<:Any,<:Hermitian}) = Hermitian(copy(transpose(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) -tr(A::Symmetric) = tr(A.data) # to avoid AbstractMatrix fallback (incl. allocations) -tr(A::Hermitian) = real(tr(A.data)) +tr(A::Symmetric{<:Number}) = tr(A.data) # to avoid AbstractMatrix fallback (incl. allocations) +tr(A::Hermitian{<:Number}) = real(tr(A.data)) Base.conj(A::Symmetric) = Symmetric(parentof_applytri(conj, A), sym_uplo(A.uplo)) Base.conj(A::Hermitian) = Hermitian(parentof_applytri(conj, A), sym_uplo(A.uplo)) diff --git a/stdlib/LinearAlgebra/test/symmetric.jl b/stdlib/LinearAlgebra/test/symmetric.jl index 89e9ca0d6a51d..5f1293ab2cdd7 100644 --- a/stdlib/LinearAlgebra/test/symmetric.jl +++ b/stdlib/LinearAlgebra/test/symmetric.jl @@ -1116,4 +1116,15 @@ end end end +@testset "tr for block matrices" begin + m = [1 2; 3 4] + for b in (m, m * (1 + im)) + M = fill(b, 3, 3) + for ST in (Symmetric, Hermitian) + S = ST(M) + @test tr(S) == sum(diag(S)) + end + end +end + end # module TestSymmetric From d17b5acf9d534d57b938735fab22078d00af7fd0 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 19 Aug 2024 19:30:17 +0530 Subject: [PATCH 02/94] Faster trace for `StridedMatrix`es (#55523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR generalizes the `tr(::Matrix)` method to accept `StridedMatrix` types. As a consequence: ```julia julia> A = rand(1000,1000); julia> V = view(A, axes(A)...); julia> @btime tr($V); 1.990 μs (3 allocations: 7.88 KiB) # nightly v"1.12.0-DEV.1063" 999.455 ns (0 allocations: 0 bytes) # this PR ``` --- stdlib/LinearAlgebra/src/dense.jl | 4 ++-- stdlib/LinearAlgebra/test/dense.jl | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/stdlib/LinearAlgebra/src/dense.jl b/stdlib/LinearAlgebra/src/dense.jl index 545801b065fb5..62096cbb172f2 100644 --- a/stdlib/LinearAlgebra/src/dense.jl +++ b/stdlib/LinearAlgebra/src/dense.jl @@ -370,10 +370,10 @@ julia> diagm([1,2,3]) diagm(v::AbstractVector) = diagm(0 => v) diagm(m::Integer, n::Integer, v::AbstractVector) = diagm(m, n, 0 => v) -function tr(A::Matrix{T}) where T +function tr(A::StridedMatrix{T}) where T checksquare(A) isempty(A) && return zero(T) - reduce(+, (A[i] for i in diagind(A))) + reduce(+, (A[i] for i in diagind(A, IndexStyle(A)))) end _kronsize(A::AbstractMatrix, B::AbstractMatrix) = map(*, size(A), size(B)) diff --git a/stdlib/LinearAlgebra/test/dense.jl b/stdlib/LinearAlgebra/test/dense.jl index b9af413ad8319..1d43d76899392 100644 --- a/stdlib/LinearAlgebra/test/dense.jl +++ b/stdlib/LinearAlgebra/test/dense.jl @@ -1290,10 +1290,14 @@ end S = [1 2; 3 4] M = fill(S, 3, 3) @test tr(M) == 3S + @test tr(view(M, :, :)) == 3S + @test tr(view(M, axes(M)...)) == 3S end @testset "avoid promotion" begin A = Int8[1 3; 2 4] @test tr(A) === Int8(5) + @test tr(view(A, :, :)) === Int8(5) + @test tr(view(A, axes(A)...)) === Int8(5) end end From fc7b40e7fc7dc020d763fe070eed747e75f0c970 Mon Sep 17 00:00:00 2001 From: Sebastian Pfitzner Date: Mon, 19 Aug 2024 16:35:41 +0200 Subject: [PATCH 03/94] Add JULIA_PKG_GC_AUTO to docs (#51583) --- doc/src/manual/environment-variables.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/src/manual/environment-variables.md b/doc/src/manual/environment-variables.md index 84f36144304aa..30f2263904f40 100644 --- a/doc/src/manual/environment-variables.md +++ b/doc/src/manual/environment-variables.md @@ -267,6 +267,14 @@ versions of packages already installed as possible. !!! compat "Julia 1.9" This only affects Julia 1.9 and above. +### [`JULIA_PKG_GC_AUTO`](@id JULIA_PKG_GC_AUTO) + +If set to `false`, automatic garbage collection of packages and artifacts will be disabled; +see [`Pkg.gc`](https://pkgdocs.julialang.org/v1/api/#Pkg.gc) for more details. + +!!! compat "Julia 1.12" + This environment variable is only supported on Julia 1.12 and above. + ## Network transport ### [`JULIA_NO_VERIFY_HOSTS`](@id JULIA_NO_VERIFY_HOSTS) From 39eaa3cfc1a861cf898d89fbe320ffa858f41939 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 19 Aug 2024 11:38:52 -0400 Subject: [PATCH 04/94] Add test for upper/lower/titlecase and fix missing import (#55443) --- base/strings/annotated.jl | 2 +- base/strings/unicode.jl | 2 +- test/strings/annotated.jl | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/base/strings/annotated.jl b/base/strings/annotated.jl index f077c577237d0..be4c6887d4a6d 100644 --- a/base/strings/annotated.jl +++ b/base/strings/annotated.jl @@ -384,7 +384,7 @@ a vector of region–annotation tuples. In accordance with the semantics documented in [`AnnotatedString`](@ref), the order of annotations returned matches the order in which they were applied. -See also: `annotate!`. +See also: [`annotate!`](@ref). """ annotations(s::AnnotatedString) = s.annotations diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index 3c6710025077c..ad047514c85a6 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -6,7 +6,7 @@ module Unicode import Base: show, ==, hash, string, Symbol, isless, length, eltype, convert, isvalid, ismalformed, isoverlong, iterate, AnnotatedString, AnnotatedChar, annotated_chartransform, - @assume_effects + @assume_effects, annotations # whether codepoints are valid Unicode scalar values, i.e. 0-0xd7ff, 0xe000-0x10ffff diff --git a/test/strings/annotated.jl b/test/strings/annotated.jl index e985c0b421a51..c8fa0680113a7 100644 --- a/test/strings/annotated.jl +++ b/test/strings/annotated.jl @@ -64,6 +64,9 @@ end @testset "AnnotatedChar" begin chr = Base.AnnotatedChar('c') @test chr == Base.AnnotatedChar(chr.char, Pair{Symbol, Any}[]) + @test uppercase(chr) == Base.AnnotatedChar('C') + @test titlecase(chr) == Base.AnnotatedChar('C') + @test lowercase(Base.AnnotatedChar('C')) == chr str = Base.AnnotatedString("hmm", [(1:1, :attr => "h0h0"), (1:2, :attr => "h0m1"), (2:3, :attr => "m1m2")]) From 62e7705845f9dbf0c22b291c3d63e3cc6af57df4 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Mon, 19 Aug 2024 12:10:29 -0700 Subject: [PATCH 05/94] Set `.jl` sources as read-only during installation (#55524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This sets all `.jl` files in `$(prefix)/base` and `$(prefix)/test` to have `0444` permissions, to better match how `Pkg` installs packages (and sets them to be read-only). Fixes https://github.com/JuliaLang/juliaup/issues/865 --------- Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com> --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 3d8bf5436b476..735d342a79eb5 100644 --- a/Makefile +++ b/Makefile @@ -382,6 +382,11 @@ endif cp -R -L $(JULIAHOME)/base/* $(DESTDIR)$(datarootdir)/julia/base cp -R -L $(JULIAHOME)/test/* $(DESTDIR)$(datarootdir)/julia/test cp -R -L $(build_datarootdir)/julia/* $(DESTDIR)$(datarootdir)/julia + + # Set .jl sources as read-only to match package directories + find $(DESTDIR)$(datarootdir)/julia/base -type f -name \*.jl -exec chmod 0444 '{}' \; + find $(DESTDIR)$(datarootdir)/julia/test -type f -name \*.jl -exec chmod 0444 '{}' \; + # Copy documentation cp -R -L $(BUILDROOT)/doc/_build/html $(DESTDIR)$(docdir)/ # Remove various files which should not be installed From 23132607d0b87a2cdd59245dc70eb7aa905df26b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 19 Aug 2024 16:30:10 -0400 Subject: [PATCH 06/94] atomics: fix setonce runtime intrinsic (#55530) --- src/datatype.c | 2 +- test/atomics.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datatype.c b/src/datatype.c index 5d35a7b55057d..fe457f8180dc9 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -2188,7 +2188,7 @@ inline int setonce_bits(jl_datatype_t *rty, char *p, jl_value_t *parent, jl_valu } else { char *px = lock(p, parent, needlock, isatomic); - success = undefref_check(rty, (jl_value_t*)px) != NULL; + success = undefref_check(rty, (jl_value_t*)px) == NULL; if (success) memassign_safe(hasptr, px, rhs, fsz); unlock(p, parent, needlock, isatomic); diff --git a/test/atomics.jl b/test/atomics.jl index 165c8dc4e2dfc..adfe4c87138cd 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -319,6 +319,7 @@ test_field_orderings(ARefxy{Union{Nothing,Missing}}(nothing, missing), nothing, test_field_orderings(ARefxy{Union{Nothing,Int}}(nothing, 123_1), nothing, 123_1) test_field_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) test_field_orderings(Complex{Real}(10, 30), Complex{Real}(20, 40)) +test_field_orderings(Complex{Rational{Integer}}(10, 30), Complex{Rational{Integer}}(20, 40)) test_field_orderings(10.0, 20.0) test_field_orderings(NaN, Inf) From 4a229bc40eb075a82498e21a59e135fd7a5f0c6b Mon Sep 17 00:00:00 2001 From: Ken Williams Date: Mon, 19 Aug 2024 17:02:09 -0500 Subject: [PATCH 07/94] Add behavior for even values to docs for new invmod(n, T) functions (#55531) As discussed in https://discourse.julialang.org/t/the-new-invmod-n-t-function-and-even-arguments/118377 --- base/intfuncs.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index f72ac6ee08d4d..e44ea8eed2463 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -263,14 +263,16 @@ end invmod(n::T) where {T <: Base.BitInteger} Compute the modular inverse of `n` in the integer ring of type `T`, i.e. modulo -`2^N` where `N = 8*sizeof(T)` (e.g. `N = 32` for `Int32`). In other words these +`2^N` where `N = 8*sizeof(T)` (e.g. `N = 32` for `Int32`). In other words, these methods satisfy the following identities: ``` n * invmod(n) == 1 (n * invmod(n, T)) % T == 1 (n % T) * invmod(n, T) == 1 ``` -Note that `*` here is modular multiplication in the integer ring, `T`. +Note that `*` here is modular multiplication in the integer ring, `T`. This will +throw an error if ``n`` is even, because then it is not relatively prime with `2^N` +and thus has no such inverse. Specifying the modulus implied by an integer type as an explicit value is often inconvenient since the modulus is by definition too big to be represented by the From bec4702fa1eb76942e75285ac9ca8aa020b39c31 Mon Sep 17 00:00:00 2001 From: Nathan Boyer <65452054+nathanrboyer@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:05:42 -0400 Subject: [PATCH 08/94] Improve `walkdir` docstring (#55476) I was not able to understand how to use `walkdir` with the current docstring. It was not clear to me that `root` changes each iteration. I thought `root` would stay fixed to the input and `dirs` would iterate. --- base/file.jl | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/base/file.jl b/base/file.jl index 3987029d5f74f..7fa4a288c1d10 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1093,7 +1093,11 @@ end walkdir(dir; topdown=true, follow_symlinks=false, onerror=throw) Return an iterator that walks the directory tree of a directory. -The iterator returns a tuple containing `(rootpath, dirs, files)`. + +The iterator returns a tuple containing `(path, dirs, files)`. +Each iteration `path` will change to the next directory in the tree; +then `dirs` and `files` will be vectors containing the directories and files +in the current `path` directory. The directory tree can be traversed top-down or bottom-up. If `walkdir` or `stat` encounters a `IOError` it will rethrow the error by default. A custom error handling function can be provided through `onerror` keyword argument. @@ -1103,14 +1107,14 @@ See also: [`readdir`](@ref). # Examples ```julia -for (root, dirs, files) in walkdir(".") - println("Directories in \$root") +for (path, dirs, files) in walkdir(".") + println("Directories in \$path") for dir in dirs - println(joinpath(root, dir)) # path to directories + println(joinpath(path, dir)) # path to directories end - println("Files in \$root") + println("Files in \$path") for file in files - println(joinpath(root, file)) # path to files + println(joinpath(path, file)) # path to files end end ``` @@ -1120,18 +1124,18 @@ julia> mkpath("my/test/dir"); julia> itr = walkdir("my"); -julia> (root, dirs, files) = first(itr) +julia> (path, dirs, files) = first(itr) ("my", ["test"], String[]) -julia> (root, dirs, files) = first(itr) +julia> (path, dirs, files) = first(itr) ("my/test", ["dir"], String[]) -julia> (root, dirs, files) = first(itr) +julia> (path, dirs, files) = first(itr) ("my/test/dir", String[], String[]) ``` """ -function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw) - function _walkdir(chnl, root) +function walkdir(path; topdown=true, follow_symlinks=false, onerror=throw) + function _walkdir(chnl, path) tryf(f, p) = try f(p) catch err @@ -1143,7 +1147,7 @@ function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw) end return end - entries = tryf(_readdirx, root) + entries = tryf(_readdirx, path) entries === nothing && return dirs = Vector{String}() files = Vector{String}() @@ -1157,17 +1161,17 @@ function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw) end if topdown - push!(chnl, (root, dirs, files)) + push!(chnl, (path, dirs, files)) end for dir in dirs - _walkdir(chnl, joinpath(root, dir)) + _walkdir(chnl, joinpath(path, dir)) end if !topdown - push!(chnl, (root, dirs, files)) + push!(chnl, (path, dirs, files)) end nothing end - return Channel{Tuple{String,Vector{String},Vector{String}}}(chnl -> _walkdir(chnl, root)) + return Channel{Tuple{String,Vector{String},Vector{String}}}(chnl -> _walkdir(chnl, path)) end function unlink(p::AbstractString) From a218e82ad3a4b5c44351decf7a734b2b30985ce3 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 19 Aug 2024 21:57:38 -0400 Subject: [PATCH 09/94] handle Type{Union{}} like typeof(Union{}) more (#55508) We could try to make them both pointers (by setting mayinlinealloc=false on Core.TypeofBottom), but let's try to make them both equivalent representations of the typeof Union{} as a singleton value. Fixes #55208 --- src/cgutils.cpp | 9 +++++++-- src/codegen.cpp | 10 ++++++---- src/datatype.c | 4 ++++ src/jltypes.c | 1 + test/compiler/codegen.jl | 15 +++++++++++++++ test/core.jl | 2 ++ 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 0969f78f10bb4..f911ef17eea38 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -650,7 +650,7 @@ static Type *_julia_type_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, jl { // this function converts a Julia Type into the equivalent LLVM type if (isboxed) *isboxed = false; - if (jt == (jl_value_t*)jl_bottom_type) + if (jt == (jl_value_t*)jl_bottom_type || jt == (jl_value_t*)jl_typeofbottom_type || jt == (jl_value_t*)jl_typeofbottom_type->super) return getVoidTy(ctxt); if (jl_is_concrete_immutable(jt)) { if (jl_datatype_nbits(jt) == 0) @@ -760,7 +760,7 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, // use this where C-compatible (unboxed) structs are desired // use julia_type_to_llvm directly when you want to preserve Julia's type semantics if (isboxed) *isboxed = false; - if (jt == (jl_value_t*)jl_bottom_type) + if (jt == (jl_value_t*)jl_bottom_type || jt == (jl_value_t*)jl_typeofbottom_type || jt == (jl_value_t*)jl_typeofbottom_type->super) return getVoidTy(ctxt); if (jl_is_primitivetype(jt)) return bitstype_to_llvm(jt, ctxt, llvmcall); @@ -948,6 +948,9 @@ static bool for_each_uniontype_small( allunbox &= for_each_uniontype_small(f, ((jl_uniontype_t*)ty)->b, counter); return allunbox; } + else if (ty == (jl_value_t*)jl_typeofbottom_type->super) { + f(++counter, jl_typeofbottom_type); // treat Tuple{union{}} as identical to typeof(Union{}) + } else if (jl_is_pointerfree(ty)) { f(++counter, (jl_datatype_t*)ty); return true; @@ -1691,6 +1694,8 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, if (intersected_type == (jl_value_t*)jl_bottom_type) known_isa = false; } + if (intersected_type == (jl_value_t*)jl_typeofbottom_type->super) + intersected_type = (jl_value_t*)jl_typeofbottom_type; // swap abstract Type{Union{}} for concrete typeof(Union{}) if (known_isa) { if (!*known_isa && !msg.isTriviallyEmpty()) { emit_type_error(ctx, x, literal_pointer_val(ctx, type), msg); diff --git a/src/codegen.cpp b/src/codegen.cpp index 6551f13ea566e..e9a58d25e3e94 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2240,6 +2240,8 @@ static inline jl_cgval_t ghostValue(jl_codectx_t &ctx, jl_value_t *typ) // replace T::Type{T} with T, by assuming that T must be a leaftype of some sort jl_cgval_t constant(NULL, true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); constant.constant = jl_tparam0(typ); + if (typ == (jl_value_t*)jl_typeofbottom_type->super) + constant.isghost = true; return constant; } return jl_cgval_t(typ); @@ -2252,7 +2254,7 @@ static inline jl_cgval_t ghostValue(jl_codectx_t &ctx, jl_datatype_t *typ) static inline jl_cgval_t mark_julia_const(jl_codectx_t &ctx, jl_value_t *jv) { jl_value_t *typ; - if (jl_is_type(jv)) { + if (jl_is_type(jv) && jv != jl_bottom_type) { typ = (jl_value_t*)jl_wrap_Type(jv); // TODO: gc-root this? } else { @@ -3619,8 +3621,8 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva if (arg1.constant && arg2.constant) return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), jl_egal(arg1.constant, arg2.constant)); - jl_value_t *rt1 = arg1.typ; - jl_value_t *rt2 = arg2.typ; + jl_value_t *rt1 = (arg1.constant ? jl_typeof(arg1.constant) : arg1.typ); + jl_value_t *rt2 = (arg2.constant ? jl_typeof(arg2.constant) : arg2.typ); if (jl_is_concrete_type(rt1) && jl_is_concrete_type(rt2) && !jl_is_kind(rt1) && !jl_is_kind(rt2) && rt1 != rt2) { // disjoint concrete leaf types are never equal (quick test) return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); @@ -9534,7 +9536,7 @@ static jl_llvm_functions_t RTindex = UndefValue::get(getInt8Ty(ctx.builder.getContext())); } else if (jl_is_concrete_type(val.typ) || val.constant) { - size_t tindex = get_box_tindex((jl_datatype_t*)val.typ, phiType); + size_t tindex = get_box_tindex((jl_datatype_t*)(val.constant ? jl_typeof(val.constant) : val.typ), phiType); if (tindex == 0) { if (VN) V = boxed(ctx, val); diff --git a/src/datatype.c b/src/datatype.c index fe457f8180dc9..1157c1d425cb2 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -357,6 +357,8 @@ int jl_struct_try_layout(jl_datatype_t *dt) int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree) { + if (jl_typeofbottom_type && ty == jl_typeofbottom_type->super) + ty = jl_typeofbottom_type; if (ty->name->mayinlinealloc && jl_struct_try_layout(ty)) { if (ty->layout->npointers > 0) { if (pointerfree) @@ -1656,6 +1658,8 @@ JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type) { jl_task_t *ct = jl_current_task; if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL || jl_is_layout_opaque(type->layout)) { + if (type == jl_typeofbottom_type->super) + return jl_bottom_type; // ::Type{Union{}} is an abstract type, but is also a singleton when used as a field type jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); } if (type->instance != NULL) diff --git a/src/jltypes.c b/src/jltypes.c index 5dc50ff0ca4e6..5516b8c9c0c6e 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3024,6 +3024,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_anytuple_type->layout = NULL; jl_typeofbottom_type->super = jl_wrap_Type(jl_bottom_type); + jl_typeofbottom_type->super->layout = jl_typeofbottom_type->layout; // the only abstract type with a layout jl_emptytuple_type = (jl_datatype_t*)jl_apply_tuple_type(jl_emptysvec, 0); jl_emptytuple = jl_gc_permobj(0, jl_emptytuple_type); jl_emptytuple_type->instance = jl_emptytuple; diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index cb983d7ab515e..a8128be270f12 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -969,3 +969,18 @@ end # Core.getptls() special handling @test !occursin("call ptr @jlplt", get_llvm(Core.getptls, Tuple{})) #It should lower to a direct load of the ptls and not a ccall + +# issue 55208 +@noinline function f55208(x, i) + z = (i == 0 ? x[1] : x[i]) + return z isa Core.TypeofBottom +end +@test f55208((Union{}, 5, 6, 7), 0) + +@noinline function g55208(x, i) + z = (i == 0 ? x[1] : x[i]) + typeof(z) +end +@test g55208((Union{}, true, true), 0) === typeof(Union{}) + +@test string((Core.Union{}, true, true, true)) == "(Union{}, true, true, true)" diff --git a/test/core.jl b/test/core.jl index 4cbb872ce4e50..c643c965f89cc 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8265,3 +8265,5 @@ end @test Tuple{Vararg{Int}} === Union{Tuple{Int}, Tuple{}, Tuple{Int, Int, Vararg{Int}}} @test (Tuple{Vararg{T}} where T) === (Union{Tuple{T, T, Vararg{T}}, Tuple{}, Tuple{T}} where T) @test_broken (Tuple{Vararg{T}} where T) === Union{Tuple{T, T, Vararg{T}} where T, Tuple{}, Tuple{T} where T} + +@test sizeof(Pair{Union{typeof(Union{}),Nothing}, Union{Type{Union{}},Nothing}}(Union{}, Union{})) == 2 From d4bd540d0f30605ef184af79f2aaaff9aa351a54 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 19 Aug 2024 21:57:48 -0400 Subject: [PATCH 10/94] make jl_thread_suspend_and_get_state safe (#55500) Fixes async safety, thread safety, FreeBSD safety. --- src/signals-unix.c | 109 +++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/src/signals-unix.c b/src/signals-unix.c index 5e79fd5a2e29e..edca523bed6d1 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -314,6 +314,8 @@ int exc_reg_is_write_fault(uintptr_t esr) { #if defined(HAVE_MACH) #include "signals-mach.c" #else +#include +#include int jl_lock_stackwalk(void) { @@ -439,17 +441,13 @@ JL_NO_ASAN static void segv_handler(int sig, siginfo_t *info, void *context) } } -#if !defined(JL_DISABLE_LIBUNWIND) -static bt_context_t *signal_context; -pthread_mutex_t in_signal_lock; -static pthread_cond_t exit_signal_cond; -static pthread_cond_t signal_caught_cond; +pthread_mutex_t in_signal_lock; // shared with jl_delete_thread +static bt_context_t *signal_context; // protected by in_signal_lock +static int exit_signal_cond = -1; +static int signal_caught_cond = -1; int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout; pthread_mutex_lock(&in_signal_lock); jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; @@ -458,48 +456,51 @@ int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) pthread_mutex_unlock(&in_signal_lock); return 0; } - jl_atomic_store_release(&ptls2->signal_request, 1); - pthread_kill(ptls2->system_id, SIGUSR2); - // wait for thread to acknowledge - int err = pthread_cond_timedwait(&signal_caught_cond, &in_signal_lock, &ts); - if (err == ETIMEDOUT) { - sig_atomic_t request = 1; + sig_atomic_t request = 0; + if (!jl_atomic_cmpswap(&ptls2->signal_request, &request, 1)) { + // something is wrong, or there is already a usr2 in flight elsewhere + pthread_mutex_unlock(&in_signal_lock); + return 0; + } + request = 1; + int err = pthread_kill(ptls2->system_id, SIGUSR2); + // wait for thread to acknowledge or timeout + struct pollfd event = {signal_caught_cond, POLLIN, 0}; + if (err == 0) { + do { + err = poll(&event, 1, timeout * 1000); + } while (err == -1 && errno == EINTR); + } + if ((event.revents & POLLIN) == 0) { + // not ready after timeout: try to cancel this request if (jl_atomic_cmpswap(&ptls2->signal_request, &request, 0)) { pthread_mutex_unlock(&in_signal_lock); return 0; } - // Request is either now 0 (meaning the other thread is waiting for - // exit_signal_cond already), - // Or it is now -1 (meaning the other thread - // is waiting for in_signal_lock, and we need to release that lock - // here for a bit, until the other thread has a chance to get to the - // exit_signal_cond) - if (request == -1) { - err = pthread_cond_wait(&signal_caught_cond, &in_signal_lock); - assert(!err); - } } + eventfd_t got; + do { + err = read(signal_caught_cond, &got, sizeof(eventfd_t)); + } while (err == -1 && errno == EINTR); + if (err != sizeof(eventfd_t)) abort(); + assert(got == 1); (void) got; // Now the other thread is waiting on exit_signal_cond (verify that here by // checking it is 0, and add an acquire barrier for good measure) - int request = jl_atomic_load_acquire(&ptls2->signal_request); + request = jl_atomic_load_acquire(&ptls2->signal_request); assert(request == 0); (void) request; - jl_atomic_store_release(&ptls2->signal_request, 1); // prepare to resume normally + jl_atomic_store_release(&ptls2->signal_request, 4); // prepare to resume normally, but later code may change this *ctx = *signal_context; return 1; } void jl_thread_resume(int tid) { - jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; - pthread_cond_broadcast(&exit_signal_cond); - pthread_cond_wait(&signal_caught_cond, &in_signal_lock); // wait for thread to acknowledge (so that signal_request doesn't get mixed up) - // The other thread is waiting to leave exit_signal_cond (verify that here by - // checking it is 0, and add an acquire barrier for good measure) - int request = jl_atomic_load_acquire(&ptls2->signal_request); - assert(request == 0); (void) request; + int err; + eventfd_t got = 1; + err = write(exit_signal_cond, &got, sizeof(eventfd_t)); + if (err != sizeof(eventfd_t)) abort(); pthread_mutex_unlock(&in_signal_lock); } -#endif // Throw jl_interrupt_exception if the master thread is in a signal async region // or if SIGINT happens too often. @@ -508,9 +509,11 @@ static void jl_try_deliver_sigint(void) jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; jl_safepoint_enable_sigint(); jl_wake_libuv(); + pthread_mutex_lock(&in_signal_lock); jl_atomic_store_release(&ptls2->signal_request, 2); // This also makes sure `sleep` is aborted. pthread_kill(ptls2->system_id, SIGUSR2); + pthread_mutex_unlock(&in_signal_lock); } // Write only by signal handling thread, read only by main thread @@ -543,12 +546,12 @@ static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) } // request: -// -1: beginning processing [invalid outside here] // 0: nothing [not from here] -// 1: get state +// 1: get state & wait for request // 2: throw sigint if `!defer_signal && io_wait` or if force throw threshold // is reached // 3: raise `thread0_exit_signo` and try to exit +// 4: no-op void usr2_handler(int sig, siginfo_t *info, void *ctx) { jl_task_t *ct = jl_get_current_task(); @@ -559,25 +562,21 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) return; int errno_save = errno; // acknowledge that we saw the signal_request - sig_atomic_t request = jl_atomic_exchange(&ptls->signal_request, -1); -#if !defined(JL_DISABLE_LIBUNWIND) + sig_atomic_t request = jl_atomic_exchange(&ptls->signal_request, 0); if (request == 1) { - pthread_mutex_lock(&in_signal_lock); signal_context = jl_to_bt_context(ctx); - // acknowledge that we set the signal_caught_cond broadcast + int err; + eventfd_t got = 1; + err = write(signal_caught_cond, &got, sizeof(eventfd_t)); + if (err != sizeof(eventfd_t)) abort(); + do { + err = read(exit_signal_cond, &got, sizeof(eventfd_t)); + } while (err == -1 && errno == EINTR); + if (err != sizeof(eventfd_t)) abort(); + assert(got == 1); request = jl_atomic_exchange(&ptls->signal_request, 0); - assert(request == -1); (void) request; - pthread_cond_broadcast(&signal_caught_cond); - pthread_cond_wait(&exit_signal_cond, &in_signal_lock); - request = jl_atomic_exchange(&ptls->signal_request, 0); - assert(request == 1 || request == 3); - // acknowledge that we got the resume signal - pthread_cond_broadcast(&signal_caught_cond); - pthread_mutex_unlock(&in_signal_lock); + assert(request == 2 || request == 3 || request == 4); } - else -#endif - jl_atomic_exchange(&ptls->signal_request, 0); // returns -1 if (request == 2) { int force = jl_check_force_sigint(); if (force || (!ptls->defer_signal && ptls->io_wait)) { @@ -1038,10 +1037,12 @@ void restore_signals(void) jl_sigsetset(&sset); pthread_sigmask(SIG_SETMASK, &sset, 0); -#if !defined(HAVE_MACH) && !defined(JL_DISABLE_LIBUNWIND) +#if !defined(HAVE_MACH) + exit_signal_cond = eventfd(0, EFD_CLOEXEC); + signal_caught_cond = eventfd(0, EFD_CLOEXEC); if (pthread_mutex_init(&in_signal_lock, NULL) != 0 || - pthread_cond_init(&exit_signal_cond, NULL) != 0 || - pthread_cond_init(&signal_caught_cond, NULL) != 0) { + exit_signal_cond == -1 || + signal_caught_cond == -1) { jl_error("SIGUSR pthread init failed"); } #endif From 9650510b5fa64571178cb9fe8b6799060ae0a3ac Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:58:22 +0900 Subject: [PATCH 11/94] inference: model partially initialized structs with `PartialStruct` (#55297) There is still room for improvement in the accuracy of `getfield` and `isdefined` for structs with uninitialized fields. This commit aims to enhance the accuracy of struct field defined-ness by propagating such struct as `PartialStruct` in cases where fields that might be uninitialized are confirmed to be defined. Specifically, the improvements are made in the following situations: 1. when a `:new` expression receives arguments greater than the minimum number of initialized fields. 2. when new information about the initialized fields of `x` can be obtained in the `then` branch of `if isdefined(x, :f)`. Combined with the existing optimizations, these improvements enable DCE in scenarios such as: ```julia julia> @noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing); julia> @allocated broadcast_noescape1(Ref("x")) 16 # master 0 # this PR ``` One important point to note is that, as revealed in JuliaLang/julia#48999, fields and globals can revert to `undef` during precompilation. This commit does not affect globals. Furthermore, even for fields, the refinements made by 1. and 2. are propagated along with data-flow, and field defined-ness information is only used when fields are confirmed to be initialized. Therefore, the same issues as JuliaLang/julia#48999 will not occur by this commit. --- base/compiler/abstractinterpretation.jl | 86 ++++--- base/compiler/ssair/passes.jl | 7 +- base/compiler/tfuncs.jl | 42 +++- base/compiler/typelattice.jl | 87 ++++--- base/compiler/typelimits.jl | 54 +++-- .../compiler/EscapeAnalysis/EscapeAnalysis.jl | 10 +- test/compiler/inference.jl | 227 +++++++++++++++--- test/tuple.jl | 6 +- 8 files changed, 393 insertions(+), 126 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 90d395600bbde..351f241878e7d 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2006,26 +2006,39 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs return Conditional(aty.slot, thentype, elsetype) end elseif f === isdefined - uty = argtypes[2] a = ssa_def_slot(fargs[2], sv) - if isa(uty, Union) && isa(a, SlotNumber) - fld = argtypes[3] - thentype = Bottom - elsetype = Bottom - for ty in uniontypes(uty) - cnd = isdefined_tfunc(𝕃ᵢ, ty, fld) - if isa(cnd, Const) - if cnd.val::Bool - thentype = thentype ⊔ ty + if isa(a, SlotNumber) + argtype2 = argtypes[2] + if isa(argtype2, Union) + fld = argtypes[3] + thentype = Bottom + elsetype = Bottom + for ty in uniontypes(argtype2) + cnd = isdefined_tfunc(𝕃ᵢ, ty, fld) + if isa(cnd, Const) + if cnd.val::Bool + thentype = thentype ⊔ ty + else + elsetype = elsetype ⊔ ty + end else + thentype = thentype ⊔ ty elsetype = elsetype ⊔ ty end - else - thentype = thentype ⊔ ty - elsetype = elsetype ⊔ ty + end + return Conditional(a, thentype, elsetype) + else + thentype = form_partially_defined_struct(argtype2, argtypes[3]) + if thentype !== nothing + elsetype = argtype2 + if rt === Const(false) + thentype = Bottom + elseif rt === Const(true) + elsetype = Bottom + end + return Conditional(a, thentype, elsetype) end end - return Conditional(a, thentype, elsetype) end end end @@ -2033,6 +2046,24 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs return rt end +function form_partially_defined_struct(@nospecialize(obj), @nospecialize(name)) + obj isa Const && return nothing # nothing to refine + name isa Const || return nothing + objt0 = widenconst(obj) + objt = unwrap_unionall(objt0) + objt isa DataType || return nothing + isabstracttype(objt) && return nothing + fldidx = try_compute_fieldidx(objt, name.val) + fldidx === nothing && return nothing + nminfld = datatype_min_ninitialized(objt) + if ismutabletype(objt) + fldidx == nminfld+1 || return nothing + else + fldidx > nminfld || return nothing + end + return PartialStruct(objt0, Any[fieldtype(objt0, i) for i = 1:fldidx]) +end + function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}, call::CallMeta) na = length(argtypes) if isvarargtype(argtypes[end]) @@ -2573,20 +2604,18 @@ function abstract_eval_new(interp::AbstractInterpreter, e::Expr, vtypes::Union{V end ats[i] = at end - # For now, don't allow: - # - Const/PartialStruct of mutables (but still allow PartialStruct of mutables - # with `const` fields if anything refined) - # - partially initialized Const/PartialStruct - if fcount == nargs - if consistent === ALWAYS_TRUE && allconst - argvals = Vector{Any}(undef, nargs) - for j in 1:nargs - argvals[j] = (ats[j]::Const).val - end - rt = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), rt, argvals, nargs)) - elseif anyrefine - rt = PartialStruct(rt, ats) + if fcount == nargs && consistent === ALWAYS_TRUE && allconst + argvals = Vector{Any}(undef, nargs) + for j in 1:nargs + argvals[j] = (ats[j]::Const).val end + rt = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), rt, argvals, nargs)) + elseif anyrefine || nargs > datatype_min_ninitialized(rt) + # propagate partially initialized struct as `PartialStruct` when: + # - any refinement information is available (`anyrefine`), or when + # - `nargs` is greater than `n_initialized` derived from the struct type + # information alone + rt = PartialStruct(rt, ats) end else rt = refine_partial_type(rt) @@ -3094,7 +3123,8 @@ end @nospecializeinfer function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, PartialStruct) fields = copy(rt.fields) - local anyrefine = false + anyrefine = !isvarargtype(rt.fields[end]) && + length(rt.fields) > datatype_min_ninitialized(unwrap_unionall(rt.typ)) 𝕃 = typeinf_lattice(info.interp) ⊏ = strictpartialorder(𝕃) for i in 1:length(fields) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 33cda9bf27d20..37d79e2bd7b0c 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1166,7 +1166,12 @@ struct IntermediaryCollector <: WalkerCallback intermediaries::SPCSet end function (walker_callback::IntermediaryCollector)(@nospecialize(def), @nospecialize(defssa::AnySSAValue)) - isa(def, Expr) || push!(walker_callback.intermediaries, defssa.id) + if !(def isa Expr) + push!(walker_callback.intermediaries, defssa.id) + if def isa PiNode + return LiftedValue(def.val) + end + end return nothing end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 9a4c761b4209b..89874b9a6df10 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -419,7 +419,7 @@ end else return Bottom end - if 1 <= idx <= datatype_min_ninitialized(a1) + if 1 ≤ idx ≤ datatype_min_ninitialized(a1) return Const(true) elseif a1.name === _NAMEDTUPLE_NAME if isconcretetype(a1) @@ -427,15 +427,21 @@ end else ns = a1.parameters[1] if isa(ns, Tuple) - return Const(1 <= idx <= length(ns)) + return Const(1 ≤ idx ≤ length(ns)) end end - elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1)) + elseif idx ≤ 0 || (!isvatuple(a1) && idx > fieldcount(a1)) return Const(false) elseif isa(arg1, Const) if !ismutabletype(a1) || isconst(a1, idx) return Const(isdefined(arg1.val, idx)) end + elseif isa(arg1, PartialStruct) + if !isvarargtype(arg1.fields[end]) + if 1 ≤ idx ≤ length(arg1.fields) + return Const(true) + end + end elseif !isvatuple(a1) fieldT = fieldtype(a1, idx) if isa(fieldT, DataType) && isbitstype(fieldT) @@ -989,27 +995,39 @@ end ⊑ = partialorder(𝕃) # If we have s00 being a const, we can potentially refine our type-based analysis above - if isa(s00, Const) || isconstType(s00) - if !isa(s00, Const) - sv = (s00::DataType).parameters[1] - else + if isa(s00, Const) || isconstType(s00) || isa(s00, PartialStruct) + if isa(s00, Const) sv = s00.val + sty = typeof(sv) + nflds = nfields(sv) + ismod = sv isa Module + elseif isa(s00, PartialStruct) + sty = unwrap_unionall(s00.typ) + nflds = fieldcount_noerror(sty) + ismod = false + else + sv = (s00::DataType).parameters[1] + sty = typeof(sv) + nflds = nfields(sv) + ismod = sv isa Module end if isa(name, Const) nval = name.val if !isa(nval, Symbol) - isa(sv, Module) && return false + ismod && return false isa(nval, Int) || return false end return isdefined_tfunc(𝕃, s00, name) === Const(true) end - boundscheck && return false + # If bounds checking is disabled and all fields are assigned, # we may assume that we don't throw - isa(sv, Module) && return false + @assert !boundscheck + ismod && return false name ⊑ Int || name ⊑ Symbol || return false - typeof(sv).name.n_uninitialized == 0 && return true - for i = (datatype_min_ninitialized(typeof(sv)) + 1):nfields(sv) + sty.name.n_uninitialized == 0 && return true + nflds === nothing && return false + for i = (datatype_min_ninitialized(sty)+1):nflds isdefined_tfunc(𝕃, s00, Const(i)) === Const(true) || return false end return true diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 1be76f7d8bea3..d375d61c0bdd8 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -6,17 +6,42 @@ # N.B.: Const/PartialStruct/InterConditional are defined in Core, to allow them to be used # inside the global code cache. -# -# # The type of a value might be constant -# struct Const -# val -# end -# -# struct PartialStruct -# typ -# fields::Vector{Any} # elements are other type lattice members -# end + import Core: Const, PartialStruct + +""" + struct Const + val + end + +The type representing a constant value. +""" +:(Const) + +""" + struct PartialStruct + typ + fields::Vector{Any} # elements are other type lattice members + end + +This extended lattice element is introduced when we have information about an object's +fields beyond what can be obtained from the object type. E.g. it represents a tuple where +some elements are known to be constants or a struct whose `Any`-typed field is initialized +with `Int` values. + +- `typ` indicates the type of the object +- `fields` holds the lattice elements corresponding to each field of the object + +If `typ` is a struct, `fields` represents the fields of the struct that are guaranteed to be +initialized. For instance, if the length of `fields` of `PartialStruct` representing a +struct with 4 fields is 3, the 4th field may not be initialized. If the length is 4, all +fields are guaranteed to be initialized. + +If `typ` is a tuple, the last element of `fields` may be `Vararg`. In this case, it is +guaranteed that the number of elements in the tuple is at least `length(fields)-1`, but the +exact number of elements is unknown. +""" +:(PartialStruct) function PartialStruct(@nospecialize(typ), fields::Vector{Any}) for i = 1:length(fields) assert_nested_slotwrapper(fields[i]) @@ -57,8 +82,13 @@ end Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = Conditional(slot_id(var), thentype, elsetype) +import Core: InterConditional """ - cnd::InterConditional + struct InterConditional + slot::Int + thentype + elsetype + end Similar to `Conditional`, but conveys inter-procedural constraints imposed on call arguments. This is separate from `Conditional` to catch logic errors: the lattice element name is `InterConditional` @@ -66,14 +96,6 @@ while processing a call, then `Conditional` everywhere else. Thus `InterConditio `CompilerTypes`—these type's usages are disjoint—though we define the lattice for `InterConditional`. """ :(InterConditional) -import Core: InterConditional -# struct InterConditional -# slot::Int -# thentype -# elsetype -# InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = -# new(slot, thentype, elsetype) -# end InterConditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = InterConditional(slot_id(var), thentype, elsetype) @@ -447,8 +469,13 @@ end @nospecializeinfer function ⊑(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, PartialStruct) if isa(b, PartialStruct) - if !(length(a.fields) == length(b.fields) && a.typ <: b.typ) - return false + a.typ <: b.typ || return false + if length(a.fields) ≠ length(b.fields) + if !(isvarargtype(a.fields[end]) || isvarargtype(b.fields[end])) + length(a.fields) ≥ length(b.fields) || return false + else + return false + end end for i in 1:length(b.fields) af = a.fields[i] @@ -471,19 +498,25 @@ end return isa(b, Type) && a.typ <: b elseif isa(b, PartialStruct) if isa(a, Const) - nf = nfields(a.val) - nf == length(b.fields) || return false widea = widenconst(a)::DataType wideb = widenconst(b) wideb′ = unwrap_unionall(wideb)::DataType widea.name === wideb′.name || return false - # We can skip the subtype check if b is a Tuple, since in that - # case, the ⊑ of the elements is sufficient. - if wideb′.name !== Tuple.name && !(widea <: wideb) - return false + if wideb′.name === Tuple.name + # We can skip the subtype check if b is a Tuple, since in that + # case, the ⊑ of the elements is sufficient. + # But for tuple comparisons, we need their lengths to be the same for now. + # TODO improve accuracy for cases when `b` contains vararg element + nfields(a.val) == length(b.fields) || return false + else + widea <: wideb || return false + # for structs we need to check that `a` has more information than `b` that may be partially initialized + n_initialized(a) ≥ length(b.fields) || return false end + nf = nfields(a.val) for i in 1:nf isdefined(a.val, i) || continue # since ∀ T Union{} ⊑ T + i > length(b.fields) && break # `a` has more information than `b` that is partially initialized struct bfᵢ = b.fields[i] if i == nf bfᵢ = unwrapva(bfᵢ) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 318ac0b5c27e5..91a44d3b117ab 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -321,6 +321,11 @@ end # even after complicated recursion and other operations on it elsewhere const issimpleenoughtupleelem = issimpleenoughtype +function n_initialized(t::Const) + nf = nfields(t.val) + return something(findfirst(i::Int->!isdefined(t.val,i), 1:nf), nf+1)-1 +end + # A simplified type_more_complex query over the extended lattice # (assumes typeb ⊑ typea) @nospecializeinfer function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) @@ -328,6 +333,13 @@ const issimpleenoughtupleelem = issimpleenoughtype typea === typeb && return true if typea isa PartialStruct aty = widenconst(typea) + if typeb isa Const + @assert length(typea.fields) ≤ n_initialized(typeb) "typeb ⊑ typea is assumed" + elseif typeb isa PartialStruct + @assert length(typea.fields) ≤ length(typeb.fields) "typeb ⊑ typea is assumed" + else + return false + end for i = 1:length(typea.fields) ai = unwrapva(typea.fields[i]) bi = fieldtype(aty, i) @@ -572,34 +584,38 @@ end # N.B. This can also be called with both typea::Const and typeb::Const to # to recover PartialStruct from `Const`s with overlapping fields. -@nospecializeinfer function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) +@nospecializeinfer function tmerge_partial_struct(𝕃::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) aty = widenconst(typea) bty = widenconst(typeb) if aty === bty - # must have egal here, since we do not create PartialStruct for non-concrete types - typea_nfields = nfields_tfunc(lattice, typea) - typeb_nfields = nfields_tfunc(lattice, typeb) - isa(typea_nfields, Const) || return nothing - isa(typeb_nfields, Const) || return nothing - type_nfields = typea_nfields.val::Int - type_nfields === typeb_nfields.val::Int || return nothing - type_nfields == 0 && return nothing - fields = Vector{Any}(undef, type_nfields) - anyrefine = false - for i = 1:type_nfields - ai = getfield_tfunc(lattice, typea, Const(i)) - bi = getfield_tfunc(lattice, typeb, Const(i)) + if typea isa PartialStruct + if typeb isa PartialStruct + nflds = min(length(typea.fields), length(typeb.fields)) + else + nflds = min(length(typea.fields), n_initialized(typeb::Const)) + end + elseif typeb isa PartialStruct + nflds = min(n_initialized(typea::Const), length(typeb.fields)) + else + nflds = min(n_initialized(typea::Const), n_initialized(typeb::Const)) + end + nflds == 0 && return nothing + fields = Vector{Any}(undef, nflds) + anyrefine = nflds > datatype_min_ninitialized(unwrap_unionall(aty)) + for i = 1:nflds + ai = getfield_tfunc(𝕃, typea, Const(i)) + bi = getfield_tfunc(𝕃, typeb, Const(i)) # N.B.: We're assuming here that !isType(aty), because that case # only arises when typea === typeb, which should have been caught # before calling this. ft = fieldtype(aty, i) - if is_lattice_equal(lattice, ai, bi) || is_lattice_equal(lattice, ai, ft) + if is_lattice_equal(𝕃, ai, bi) || is_lattice_equal(𝕃, ai, ft) # Since ai===bi, the given type has no restrictions on complexity. # and can be used to refine ft tyi = ai - elseif is_lattice_equal(lattice, bi, ft) + elseif is_lattice_equal(𝕃, bi, ft) tyi = bi - elseif (tyi′ = tmerge_field(lattice, ai, bi); tyi′ !== nothing) + elseif (tyi′ = tmerge_field(𝕃, ai, bi); tyi′ !== nothing) # allow external lattice implementation to provide a custom field-merge strategy tyi = tyi′ else @@ -621,8 +637,8 @@ end end fields[i] = tyi if !anyrefine - anyrefine = has_nontrivial_extended_info(lattice, tyi) || # extended information - ⋤(lattice, tyi, ft) # just a type-level information, but more precise than the declared type + anyrefine = has_nontrivial_extended_info(𝕃, tyi) || # extended information + ⋤(𝕃, tyi, ft) # just a type-level information, but more precise than the declared type end end anyrefine && return PartialStruct(aty, fields) diff --git a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl index d8ea8be21fe07..31c21f7228014 100644 --- a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl +++ b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl @@ -2139,21 +2139,13 @@ end # ======================== # propagate escapes imposed on call arguments -@noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) -let result = code_escapes() do - broadcast_noescape1(Ref("Hi")) - end - i = only(findall(isnew, result.ir.stmts.stmt)) - @test !has_return_escape(result.state[SSAValue(i)]) - @test_broken !has_thrown_escape(result.state[SSAValue(i)]) # TODO `getfield(RefValue{String}, :x)` isn't safe -end @noinline broadcast_noescape2(b) = broadcast(identity, b) let result = code_escapes() do broadcast_noescape2(Ref("Hi")) end i = only(findall(isnew, result.ir.stmts.stmt)) @test_broken !has_return_escape(result.state[SSAValue(i)]) # TODO interprocedural alias analysis - @test_broken !has_thrown_escape(result.state[SSAValue(i)]) # TODO `getfield(RefValue{String}, :x)` isn't safe + @test !has_thrown_escape(result.state[SSAValue(i)]) end @noinline allescape_argument(a) = (global GV = a) # obvious escape let result = code_escapes() do diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 9ae98b884bef4..75f33a280e245 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1538,7 +1538,7 @@ let nfields_tfunc(@nospecialize xs...) = @test sizeof_nothrow(String) @test !sizeof_nothrow(Type{String}) @test sizeof_tfunc(Type{Union{Int64, Int32}}) == Const(Core.sizeof(Union{Int64, Int32})) - let PT = Core.Compiler.PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) + let PT = Core.PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) @test sizeof_tfunc(PT) === Const(16) @test nfields_tfunc(PT) === Const(2) @test sizeof_nothrow(PT) @@ -4743,32 +4743,80 @@ end # issue #43784 @testset "issue #43784" begin - init = Base.ImmutableDict{Any,Any}() - a = Const(init) - b = Core.PartialStruct(typeof(init), Any[Const(init), Any, Any]) - c = Core.Compiler.tmerge(a, b) - @test ⊑(a, c) - @test ⊑(b, c) - - init = Base.ImmutableDict{Number,Number}() - a = Const(init) - b = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), Any, ComplexF64]) - c = Core.Compiler.tmerge(a, b) - @test ⊑(a, c) && ⊑(b, c) - @test c === typeof(init) - - a = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), ComplexF64, ComplexF64]) - c = Core.Compiler.tmerge(a, b) - @test ⊑(a, c) && ⊑(b, c) - @test c.fields[2] === Any # or Number - @test c.fields[3] === ComplexF64 - - b = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), ComplexF32, Union{ComplexF32,ComplexF64}]) - c = Core.Compiler.tmerge(a, b) - @test ⊑(a, c) - @test ⊑(b, c) - @test c.fields[2] === Complex - @test c.fields[3] === Complex + ⊑ = Core.Compiler.partialorder(Core.Compiler.fallback_lattice) + ⊔ = Core.Compiler.join(Core.Compiler.fallback_lattice) + Const, PartialStruct = Core.Const, Core.PartialStruct + + let init = Base.ImmutableDict{Any,Any}() + a = Const(init) + b = PartialStruct(typeof(init), Any[Const(init), Any, Any]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c === typeof(init) + end + let init = Base.ImmutableDict{Any,Any}(1,2) + a = Const(init) + b = PartialStruct(typeof(init), Any[Const(getfield(init,1)), Any, Any]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c isa PartialStruct + @test length(c.fields) == 3 + end + let init = Base.ImmutableDict{Number,Number}() + a = Const(init) + b = PartialStruct(typeof(init), Any[Const(init), Number, ComplexF64]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c === typeof(init) + end + let init = Base.ImmutableDict{Number,Number}() + a = PartialStruct(typeof(init), Any[Const(init), ComplexF64, ComplexF64]) + b = PartialStruct(typeof(init), Any[Const(init), Number, ComplexF64]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c isa PartialStruct + @test c.fields[2] === Number + @test c.fields[3] === ComplexF64 + end + let init = Base.ImmutableDict{Number,Number}() + a = PartialStruct(typeof(init), Any[Const(init), ComplexF64, ComplexF64]) + b = PartialStruct(typeof(init), Any[Const(init), ComplexF32, Union{ComplexF32,ComplexF64}]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c isa PartialStruct + @test c.fields[2] === Complex + @test c.fields[3] === Complex + end + let T = Base.ImmutableDict{Number,Number} + a = PartialStruct(T, Any[T]) + b = PartialStruct(T, Any[T, Number, Number]) + @test b ⊑ a + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c isa PartialStruct + @test length(c.fields) == 1 + end + let T = Base.ImmutableDict{Number,Number} + a = PartialStruct(T, Any[T]) + b = Const(T()) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c === T + end + let T = Base.ImmutableDict{Number,Number} + a = Const(T()) + b = PartialStruct(T, Any[T]) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c === T + end + let T = Base.ImmutableDict{Number,Number} + a = Const(T()) + b = Const(T(1,2)) + c = a ⊔ b + @test a ⊑ c && b ⊑ c + @test c === T + end global const ginit43784 = Base.ImmutableDict{Any,Any}() @test Base.return_types() do @@ -4802,6 +4850,31 @@ end @test a == Tuple end +let ⊑ = Core.Compiler.partialorder(Core.Compiler.fallback_lattice) + ⊔ = Core.Compiler.join(Core.Compiler.fallback_lattice) + Const, PartialStruct = Core.Const, Core.PartialStruct + + @test (Const((1,2)) ⊑ PartialStruct(Tuple{Int,Int}, Any[Const(1),Int])) + @test !(Const((1,2)) ⊑ PartialStruct(Tuple{Int,Int,Int}, Any[Const(1),Int,Int])) + @test !(Const((1,2,3)) ⊑ PartialStruct(Tuple{Int,Int}, Any[Const(1),Int])) + @test (Const((1,2,3)) ⊑ PartialStruct(Tuple{Int,Int,Int}, Any[Const(1),Int,Int])) + @test (Const((1,2)) ⊑ PartialStruct(Tuple{Int,Vararg{Int}}, Any[Const(1),Vararg{Int}])) + @test (Const((1,2)) ⊑ PartialStruct(Tuple{Int,Int,Vararg{Int}}, Any[Const(1),Int,Vararg{Int}])) broken=true + @test (Const((1,2,3)) ⊑ PartialStruct(Tuple{Int,Int,Vararg{Int}}, Any[Const(1),Int,Vararg{Int}])) + @test !(PartialStruct(Tuple{Int,Int}, Any[Const(1),Int]) ⊑ Const((1,2))) + @test !(PartialStruct(Tuple{Int,Int,Int}, Any[Const(1),Int,Int]) ⊑ Const((1,2))) + @test !(PartialStruct(Tuple{Int,Int}, Any[Const(1),Int]) ⊑ Const((1,2,3))) + @test !(PartialStruct(Tuple{Int,Int,Int}, Any[Const(1),Int,Int]) ⊑ Const((1,2,3))) + @test !(PartialStruct(Tuple{Int,Vararg{Int}}, Any[Const(1),Vararg{Int}]) ⊑ Const((1,2))) + @test !(PartialStruct(Tuple{Int,Int,Vararg{Int}}, Any[Const(1),Int,Vararg{Int}]) ⊑ Const((1,2))) + @test !(PartialStruct(Tuple{Int,Int,Vararg{Int}}, Any[Const(1),Int,Vararg{Int}]) ⊑ Const((1,2,3))) + + t = Const((false, false)) ⊔ Const((false, true)) + @test t isa PartialStruct && length(t.fields) == 2 && t.fields[1] === Const(false) + t = t ⊔ Const((false, false, 0)) + @test t ⊑ Union{Tuple{Bool,Bool},Tuple{Bool,Bool,Int}} +end + # Test that a function-wise `@max_methods` works as expected Base.Experimental.@max_methods 1 function f_max_methods end f_max_methods(x::Int) = 1 @@ -5867,6 +5940,106 @@ bar54341(args...) = foo54341(4, args...) @test Core.Compiler.return_type(bar54341, Tuple{Vararg{Int}}) === Int +# `PartialStruct` for partially initialized structs: +struct PartiallyInitialized1 + a; b; c + PartiallyInitialized1(a) = (@nospecialize; new(a)) + PartiallyInitialized1(a, b) = (@nospecialize; new(a, b)) + PartiallyInitialized1(a, b, c) = (@nospecialize; new(a, b, c)) +end +mutable struct PartiallyInitialized2 + a; b; c + PartiallyInitialized2(a) = (@nospecialize; new(a)) + PartiallyInitialized2(a, b) = (@nospecialize; new(a, b)) + PartiallyInitialized2(a, b, c) = (@nospecialize; new(a, b, c)) +end + +# 1. isdefined modeling for partial struct +@test Base.infer_return_type((Any,Any)) do a, b + Val(isdefined(PartiallyInitialized1(a, b), :b)) +end == Val{true} +@test Base.infer_return_type((Any,Any,)) do a, b + Val(isdefined(PartiallyInitialized1(a, b), :c)) +end >: Val{false} +@test Base.infer_return_type((PartiallyInitialized1,)) do x + @assert isdefined(x, :a) + return Val(isdefined(x, :c)) +end == Val +@test Base.infer_return_type((Any,Any,Any)) do a, b, c + Val(isdefined(PartiallyInitialized1(a, b, c), :c)) +end == Val{true} +@test Base.infer_return_type((Any,Any)) do a, b + Val(isdefined(PartiallyInitialized2(a, b), :b)) +end == Val{true} +@test Base.infer_return_type((Any,Any,)) do a, b + Val(isdefined(PartiallyInitialized2(a, b), :c)) +end >: Val{false} +@test Base.infer_return_type((Any,Any,Any)) do a, b, c + s = PartiallyInitialized2(a, b) + s.c = c + Val(isdefined(s, :c)) +end >: Val{true} +@test Base.infer_return_type((Any,Any,Any)) do a, b, c + Val(isdefined(PartiallyInitialized2(a, b, c), :c)) +end == Val{true} +@test Base.infer_return_type((Vector{Int},)) do xs + Val(isdefined(tuple(1, xs...), 1)) +end == Val{true} +@test Base.infer_return_type((Vector{Int},)) do xs + Val(isdefined(tuple(1, xs...), 2)) +end == Val + +# 2. getfield modeling for partial struct +@test Base.infer_effects((Any,Any); optimize=false) do a, b + getfield(PartiallyInitialized1(a, b), :b) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Symbol,); optimize=false) do a, b, f + getfield(PartiallyInitialized1(a, b), f, #=boundscheck=#false) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Any); optimize=false) do a, b, c + getfield(PartiallyInitialized1(a, b, c), :c) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Any,Symbol); optimize=false) do a, b, c, f + getfield(PartiallyInitialized1(a, b, c), f, #=boundscheck=#false) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any); optimize=false) do a, b + getfield(PartiallyInitialized2(a, b), :b) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Symbol,); optimize=false) do a, b, f + getfield(PartiallyInitialized2(a, b), f, #=boundscheck=#false) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Any); optimize=false) do a, b, c + getfield(PartiallyInitialized2(a, b, c), :c) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any,Any,Symbol); optimize=false) do a, b, c, f + getfield(PartiallyInitialized2(a, b, c), f, #=boundscheck=#false) +end |> Core.Compiler.is_nothrow + +# isdefined-Conditionals +@test Base.infer_effects((Base.RefValue{Any},)) do x + if isdefined(x, :x) + return getfield(x, :x) + end +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Base.RefValue{Any},)) do x + if isassigned(x) + return x[] + end +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Any,Any); optimize=false) do a, c + x = PartiallyInitialized2(a) + x.c = c + if isdefined(x, :c) + return x.b + end +end |> !Core.Compiler.is_nothrow + +# End to end test case for the partially initialized struct with `PartialStruct` +@noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) +@test fully_eliminated() do + broadcast_noescape1(Ref("x")) +end + # InterConditional rt with Vararg argtypes fcondvarargs(a, b, c, d) = isa(d, Int64) gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, Int64) diff --git a/test/tuple.jl b/test/tuple.jl index b1894bd2bb6ce..355ad965f9584 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -533,7 +533,7 @@ end @test ntuple(identity, Val(n)) == ntuple(identity, n) end - @test Core.Compiler.return_type(ntuple, Tuple{typeof(identity), Val}) == Tuple{Vararg{Int}} + @test Base.infer_return_type(ntuple, Tuple{typeof(identity), Val}) == Tuple{Vararg{Int}} end struct A_15703{N} @@ -835,8 +835,8 @@ end @test @inferred(Base.circshift(t3, 7)) == ('b', 'c', 'd', 'a') @test @inferred(Base.circshift(t3, -1)) == ('b', 'c', 'd', 'a') @test_throws MethodError circshift(t1, 'a') - @test Core.Compiler.return_type(circshift, Tuple{Tuple,Integer}) <: Tuple - @test Core.Compiler.return_type(circshift, Tuple{Tuple{Vararg{Any,10}},Integer}) <: Tuple{Vararg{Any,10}} + @test Base.infer_return_type(circshift, Tuple{Tuple,Integer}) <: Tuple + @test Base.infer_return_type(circshift, Tuple{Tuple{Vararg{Any,10}},Integer}) <: Tuple{Vararg{Any,10}} for len ∈ 0:5 v = 1:len t = Tuple(v) From 31d413eff6066c8d93e77a85338cbc0413a45174 Mon Sep 17 00:00:00 2001 From: Nathan Boyer <65452054+nathanrboyer@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:31:31 -0400 Subject: [PATCH 12/94] Explain `walkdir` docstring second example. (#55541) This is a follow-up documentation PR to #55476. I believe the second example in the `walkdir` docstring is still unintuitive since the result changes each time. I attempted a simple explanation, but I don't really know what I'm talking about. Hopefully someone else can explain what is happening better. Some additional discussion in [this Discourse post](https://discourse.julialang.org/t/find-all-files-named-findthis-csv-in-nested-subfolders-of-rootfolder/118096). --------- Co-authored-by: Lilith Orion Hafner --- base/file.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/file.jl b/base/file.jl index 7fa4a288c1d10..81bca9dd65577 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1102,6 +1102,8 @@ The directory tree can be traversed top-down or bottom-up. If `walkdir` or `stat` encounters a `IOError` it will rethrow the error by default. A custom error handling function can be provided through `onerror` keyword argument. `onerror` is called with a `IOError` as argument. +The returned iterator is stateful so when accessed repeatedly each access will +resume where the last left off, like [`Iterators.Stateful`](@ref). See also: [`readdir`](@ref). From f2f76d8833541091ef13a47c49bb77b2263d9937 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 20 Aug 2024 14:39:55 -0300 Subject: [PATCH 13/94] Mark the _state field as atomic and move to proper atomics instead of llvmcall (#55502) --- base/task.jl | 16 +++------------- src/jltypes.c | 2 ++ stdlib/Serialization/src/Serialization.jl | 6 +++--- test/channels.jl | 2 +- test/core.jl | 1 + 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/base/task.jl b/base/task.jl index 5e4af6747f128..6cb1ff785eeee 100644 --- a/base/task.jl +++ b/base/task.jl @@ -156,20 +156,10 @@ const task_state_runnable = UInt8(0) const task_state_done = UInt8(1) const task_state_failed = UInt8(2) -const _state_index = findfirst(==(:_state), fieldnames(Task)) -@eval function load_state_acquire(t) - # TODO: Replace this by proper atomic operations when available - @GC.preserve t llvmcall($(""" - %rv = load atomic i8, i8* %0 acquire, align 8 - ret i8 %rv - """), UInt8, Tuple{Ptr{UInt8}}, - Ptr{UInt8}(pointer_from_objref(t) + fieldoffset(Task, _state_index))) -end - @inline function getproperty(t::Task, field::Symbol) if field === :state # TODO: this field name should be deprecated in 2.0 - st = load_state_acquire(t) + st = @atomic :acquire t._state if st === task_state_runnable return :runnable elseif st === task_state_done @@ -223,7 +213,7 @@ julia> istaskdone(b) true ``` """ -istaskdone(t::Task) = load_state_acquire(t) !== task_state_runnable +istaskdone(t::Task) = (@atomic :acquire t._state) !== task_state_runnable """ istaskstarted(t::Task) -> Bool @@ -267,7 +257,7 @@ true !!! compat "Julia 1.3" This function requires at least Julia 1.3. """ -istaskfailed(t::Task) = (load_state_acquire(t) === task_state_failed) +istaskfailed(t::Task) = ((@atomic :acquire t._state) === task_state_failed) Threads.threadid(t::Task) = Int(ccall(:jl_get_task_tid, Int16, (Any,), t)+1) function Threads.threadpool(t::Task) diff --git a/src/jltypes.c b/src/jltypes.c index 5516b8c9c0c6e..a587552aaa011 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3662,6 +3662,8 @@ void jl_init_types(void) JL_GC_DISABLED XX(task); jl_value_t *listt = jl_new_struct(jl_uniontype_type, jl_task_type, jl_nothing_type); jl_svecset(jl_task_type->types, 0, listt); + const static uint32_t task_atomicfields[1] = {0x00001000}; // Set fields 13 as atomic + jl_task_type->name->atomicfields = task_atomicfields; tv = jl_svec2(tvar("A"), tvar("R")); jl_opaque_closure_type = (jl_unionall_t*)jl_new_datatype(jl_symbol("OpaqueClosure"), core, jl_function_type, tv, diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 7600457812f66..bc476181e5b0d 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1570,11 +1570,11 @@ function deserialize(s::AbstractSerializer, ::Type{Task}) t.storage = deserialize(s) state = deserialize(s) if state === :runnable - t._state = Base.task_state_runnable + @atomic :release t._state = Base.task_state_runnable elseif state === :done - t._state = Base.task_state_done + @atomic :release t._state = Base.task_state_done elseif state === :failed - t._state = Base.task_state_failed + @atomic :release t._state = Base.task_state_failed else @assert false end diff --git a/test/channels.jl b/test/channels.jl index f1642de1b7bec..d3415a96afd12 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -382,7 +382,7 @@ end """error in running finalizer: ErrorException("task switch not allowed from inside gc finalizer")""", output)) # test for invalid state in Workqueue during yield t = @async nothing - t._state = 66 + @atomic t._state = 66 newstderr = redirect_stderr() try errstream = @async read(newstderr[1], String) diff --git a/test/core.jl b/test/core.jl index c643c965f89cc..692d91b6e05b3 100644 --- a/test/core.jl +++ b/test/core.jl @@ -42,6 +42,7 @@ for (T, c) in ( (DataType, [:types, :layout]), (Core.Memory, []), (Core.GenericMemoryRef, []), + (Task, [:_state]) ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if Base.isfieldatomic(T, i))) == Set(c) end From 7b8dd90a1342f7862e9e3c8a87b50d66dded9b40 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 20 Aug 2024 16:07:06 -0400 Subject: [PATCH 14/94] inference: represent callers_in_cycle with view slices of a stack instead of separate lists (#55364) Inspired by Tarjan's SCC, this changes the recursion representation to use a single list instead of a linked-list + merged array of cycles. --- base/compiler/abstractinterpretation.jl | 33 +++-- base/compiler/inferencestate.jl | 164 +++++++++++++++-------- base/compiler/ssair/irinterp.jl | 8 +- base/compiler/typeinfer.jl | 146 +++++++++----------- stdlib/InteractiveUtils/test/runtests.jl | 20 ++- stdlib/REPL/src/REPLCompletions.jl | 4 +- 6 files changed, 219 insertions(+), 156 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 351f241878e7d..83abfc952bf8e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -793,10 +793,10 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, # otherwise, we don't # check in the cycle list first - # all items in here are mutual parents of all others + # all items in here are considered mutual parents of all others if !any(p::AbsIntState->matches_sv(p, sv), callers_in_cycle(frame)) let parent = frame_parent(frame) - parent !== nothing || return false + parent === nothing && return false (is_cached(parent) || frame_parent(parent) !== nothing) || return false matches_sv(parent, sv) || return false end @@ -1298,7 +1298,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, if code !== nothing irsv = IRInterpretationState(interp, code, mi, arginfo.argtypes, world) if irsv !== nothing - irsv.parent = sv + assign_parentchild!(irsv, sv) rt, (nothrow, noub) = ir_abstract_constant_propagation(interp, irsv) @assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from irinterp" if !(isa(rt, Type) && hasintersect(rt, Bool)) @@ -1376,11 +1376,17 @@ function const_prop_call(interp::AbstractInterpreter, add_remark!(interp, sv, "[constprop] Could not retrieve the source") return nothing # this is probably a bad generated function (unsound), but just ignore it end - frame.parent = sv + assign_parentchild!(frame, sv) if !typeinf(interp, frame) add_remark!(interp, sv, "[constprop] Fresh constant inference hit a cycle") + @assert frame.frameid != 0 && frame.cycleid == frame.frameid + callstack = frame.callstack::Vector{AbsIntState} + @assert callstack[end] === frame && length(callstack) == frame.frameid + pop!(callstack) return nothing end + @assert frame.frameid != 0 && frame.cycleid == frame.frameid + @assert frame.parentid == sv.frameid @assert inf_result.result !== nothing # ConditionalSimpleArgtypes is allowed, because the only case in which it modifies # the argtypes is when one of the argtypes is a `Conditional`, which case @@ -3328,7 +3334,6 @@ end # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !is_inferred(frame) - frame.dont_work_on_me = true # mark that this function is currently on the stack W = frame.ip ssavaluetypes = frame.ssavaluetypes bbs = frame.cfg.blocks @@ -3549,7 +3554,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end end # while currbb <= nbbs - frame.dont_work_on_me = false nothing end @@ -3601,16 +3605,23 @@ end # make as much progress on `frame` as possible (by handling cycles) function typeinf_nocycle(interp::AbstractInterpreter, frame::InferenceState) typeinf_local(interp, frame) + @assert isempty(frame.ip) + callstack = frame.callstack::Vector{AbsIntState} + frame.cycleid == length(callstack) && return true - # If the current frame is part of a cycle, solve the cycle before finishing no_active_ips_in_callers = false - while !no_active_ips_in_callers + while true + # If the current frame is not the top part of a cycle, continue to the top of the cycle before resuming work + frame.cycleid == frame.frameid || return false + # If done, return and finalize this cycle + no_active_ips_in_callers && return true + # Otherwise, do at least one iteration over the entire current cycle no_active_ips_in_callers = true - for caller in frame.callers_in_cycle - caller.dont_work_on_me && return false # cycle is above us on the stack + for i = reverse(frame.cycleid:length(callstack)) + caller = callstack[i]::InferenceState if !isempty(caller.ip) # Note that `typeinf_local(interp, caller)` can potentially modify the other frames - # `frame.callers_in_cycle`, which is why making incremental progress requires the + # `frame.cycleid`, which is why making incremental progress requires the # outer while loop. typeinf_local(interp, caller) no_active_ips_in_callers = false diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 87647628f772e..6953dea5b9bd7 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -209,10 +209,10 @@ to enable flow-sensitive analysis. """ const VarTable = Vector{VarState} -const CACHE_MODE_NULL = 0x00 # not cached, without optimization -const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization allowed -const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization allowed -const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization allowed +const CACHE_MODE_NULL = 0x00 # not cached, optimization optional +const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization required +const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization required +const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization required mutable struct TryCatchFrame exct @@ -254,9 +254,12 @@ mutable struct InferenceState pclimitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on currpc ssavalue limitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on return cycle_backedges::Vector{Tuple{InferenceState, Int}} # call-graph backedges connecting from callee to caller - callers_in_cycle::Vector{InferenceState} - dont_work_on_me::Bool - parent # ::Union{Nothing,AbsIntState} + + # IPO tracking of in-process work, shared with all frames given AbstractInterpreter + callstack #::Vector{AbsIntState} + parentid::Int # index into callstack of the parent frame that originally added this frame (call frame_parent to extract the current parent of the SCC) + frameid::Int # index into callstack at which this object is found (or zero, if this is not a cached frame and has no parent) + cycleid::Int # index into the callstack of the topmost frame in the cycle (all frames in the same cycle share the same cycleid) #= results =# result::InferenceResult # remember where to put the result @@ -324,9 +327,7 @@ mutable struct InferenceState pclimitations = IdSet{InferenceState}() limitations = IdSet{InferenceState}() cycle_backedges = Vector{Tuple{InferenceState,Int}}() - callers_in_cycle = Vector{InferenceState}() - dont_work_on_me = false - parent = nothing + callstack = AbsIntState[] valid_worlds = WorldRange(1, get_world_counter()) bestguess = Bottom @@ -347,17 +348,23 @@ mutable struct InferenceState restrict_abstract_call_sites = isa(def, Module) - # some more setups - !iszero(cache_mode & CACHE_MODE_LOCAL) && push!(get_inference_cache(interp), result) - this = new( mi, world, mod, sptypes, slottypes, src, cfg, method_info, currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, - pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, + pclimitations, limitations, cycle_backedges, callstack, 0, 0, 0, result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, interp) + # some more setups + if !iszero(cache_mode & CACHE_MODE_LOCAL) + push!(get_inference_cache(interp), result) + end + if !iszero(cache_mode & CACHE_MODE_GLOBAL) + push!(callstack, this) + this.cycleid = this.frameid = length(callstack) + end + # Apply generated function restrictions if src.min_world != 1 || src.max_world != typemax(UInt) # From generated functions @@ -769,30 +776,6 @@ function empty_backedges!(frame::InferenceState, currpc::Int=frame.currpc) return nothing end -function print_callstack(sv::InferenceState) - print("=================== Callstack: ==================\n") - idx = 0 - while sv !== nothing - print("[") - print(idx) - if !isa(sv.interp, NativeInterpreter) - print(", ") - print(typeof(sv.interp)) - end - print("] ") - print(sv.linfo) - is_cached(sv) || print(" [uncached]") - println() - for cycle in sv.callers_in_cycle - print(' ', cycle.linfo) - println() - end - sv = sv.parent - idx += 1 - end - print("================= End callstack ==================\n") -end - function narguments(sv::InferenceState, include_va::Bool=true) nargs = Int(sv.src.nargs) if !include_va @@ -818,7 +801,9 @@ mutable struct IRInterpretationState const lazyreachability::LazyCFGReachability valid_worlds::WorldRange const edges::Vector{Any} - parent # ::Union{Nothing,AbsIntState} + callstack #::Vector{AbsIntState} + frameid::Int + parentid::Int function IRInterpretationState(interp::AbstractInterpreter, method_info::MethodInfo, ir::IRCode, mi::MethodInstance, argtypes::Vector{Any}, @@ -841,9 +826,9 @@ mutable struct IRInterpretationState lazyreachability = LazyCFGReachability(ir) valid_worlds = WorldRange(min_world, max_world == typemax(UInt) ? get_world_counter() : max_world) edges = Any[] - parent = nothing + callstack = AbsIntState[] return new(method_info, ir, mi, world, curridx, argtypes_refined, ir.sptypes, tpdum, - ssa_refined, lazyreachability, valid_worlds, edges, parent) + ssa_refined, lazyreachability, valid_worlds, edges, callstack, 0, 0) end end @@ -863,11 +848,34 @@ function IRInterpretationState(interp::AbstractInterpreter, codeinst.min_world, codeinst.max_world) end + # AbsIntState # =========== const AbsIntState = Union{InferenceState,IRInterpretationState} +function print_callstack(frame::AbsIntState) + print("=================== Callstack: ==================\n") + frames = frame.callstack::Vector{AbsIntState} + for idx = (frame.frameid == 0 ? 0 : 1):length(frames) + sv = (idx == 0 ? frame : frames[idx]) + idx == frame.frameid && print("*") + print("[") + print(idx) + if sv isa InferenceState && !isa(sv.interp, NativeInterpreter) + print(", ") + print(typeof(sv.interp)) + end + print("] ") + print(frame_instance(sv)) + is_cached(sv) || print(" [uncached]") + sv.parentid == idx - 1 || print(" [parent=", sv.parentid, "]") + println() + @assert sv.frameid == idx + end + print("================= End callstack ==================\n") +end + frame_instance(sv::InferenceState) = sv.linfo frame_instance(sv::IRInterpretationState) = sv.mi @@ -878,8 +886,32 @@ function frame_module(sv::AbsIntState) return def.module end -frame_parent(sv::InferenceState) = sv.parent::Union{Nothing,AbsIntState} -frame_parent(sv::IRInterpretationState) = sv.parent::Union{Nothing,AbsIntState} +function frame_parent(sv::InferenceState) + sv.parentid == 0 && return nothing + callstack = sv.callstack::Vector{AbsIntState} + sv = callstack[sv.cycleid]::InferenceState + sv.parentid == 0 && return nothing + return callstack[sv.parentid] +end +frame_parent(sv::IRInterpretationState) = sv.parentid == 0 ? nothing : (sv.callstack::Vector{AbsIntState})[sv.parentid] + +# add the orphan child to the parent and the parent to the child +function assign_parentchild!(child::InferenceState, parent::AbsIntState) + @assert child.frameid in (0, 1) + child.callstack = callstack = parent.callstack::Vector{AbsIntState} + child.parentid = parent.frameid + push!(callstack, child) + child.cycleid = child.frameid = length(callstack) + nothing +end +function assign_parentchild!(child::IRInterpretationState, parent::AbsIntState) + @assert child.frameid in (0, 1) + child.callstack = callstack = parent.callstack::Vector{AbsIntState} + child.parentid = parent.frameid + push!(callstack, child) + child.frameid = length(callstack) + nothing +end function is_constproped(sv::InferenceState) (;overridden_by_const) = sv.result @@ -899,9 +931,6 @@ method_for_inference_limit_heuristics(sv::AbsIntState) = method_info(sv).method_ frame_world(sv::InferenceState) = sv.world frame_world(sv::IRInterpretationState) = sv.world -callers_in_cycle(sv::InferenceState) = sv.callers_in_cycle -callers_in_cycle(sv::IRInterpretationState) = () - function is_effect_overridden(sv::AbsIntState, effect::Symbol) if is_effect_overridden(frame_instance(sv), effect) return true @@ -938,20 +967,39 @@ Note that cycles may be visited in any order. struct AbsIntStackUnwind sv::AbsIntState end -iterate(unw::AbsIntStackUnwind) = (unw.sv, (unw.sv, 0)) -function iterate(unw::AbsIntStackUnwind, (sv, cyclei)::Tuple{AbsIntState, Int}) - # iterate through the cycle before walking to the parent - callers = callers_in_cycle(sv) - if callers !== () && cyclei < length(callers) - cyclei += 1 - parent = callers[cyclei] - else - cyclei = 0 - parent = frame_parent(sv) +iterate(unw::AbsIntStackUnwind) = (unw.sv, length(unw.sv.callstack::Vector{AbsIntState})) +function iterate(unw::AbsIntStackUnwind, frame::Int) + frame == 0 && return nothing + return ((unw.sv.callstack::Vector{AbsIntState})[frame], frame - 1) +end + +struct AbsIntCycle + frames::Vector{AbsIntState} + cycleid::Int + cycletop::Int +end +iterate(unw::AbsIntCycle) = unw.cycleid == 0 ? nothing : (unw.frames[unw.cycletop], unw.cycletop) +function iterate(unw::AbsIntCycle, frame::Int) + frame == unw.cycleid && return nothing + return (unw.frames[frame - 1], frame - 1) +end + +""" + callers_in_cycle(sv::AbsIntState) + +Iterate through all callers of the given `AbsIntState` in the abstract +interpretation stack (including the given `AbsIntState` itself) that are part +of the same cycle, only if it is part of a cycle with multiple frames. +""" +function callers_in_cycle(sv::InferenceState) + callstack = sv.callstack::Vector{AbsIntState} + cycletop = cycleid = sv.cycleid + while cycletop < length(callstack) && (callstack[cycletop + 1]::InferenceState).cycleid == cycleid + cycletop += 1 end - parent === nothing && return nothing - return (parent, (parent, cyclei)) + return AbsIntCycle(callstack, cycletop == cycleid ? 0 : cycleid, cycletop) end +callers_in_cycle(sv::IRInterpretationState) = AbsIntCycle(sv.callstack::Vector{AbsIntState}, 0, 0) # temporarily accumulate our edges to later add as backedges in the callee function add_backedge!(caller::InferenceState, mi::MethodInstance) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 83881354e494e..3d49be33f39d5 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -24,7 +24,7 @@ function concrete_eval_invoke(interp::AbstractInterpreter, ci::CodeInstance, arg end newirsv = IRInterpretationState(interp, ci, mi, argtypes, world) if newirsv !== nothing - newirsv.parent = parent + assign_parentchild!(newirsv, parent) return ir_abstract_constant_propagation(interp, newirsv) end return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) @@ -440,6 +440,12 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR store_backedges(frame_instance(irsv), irsv.edges) end + if irsv.frameid != 0 + callstack = irsv.callstack::Vector{AbsIntState} + @assert callstack[end] === irsv && length(callstack) == irsv.frameid + pop!(callstack) + end + return Pair{Any,Tuple{Bool,Bool}}(maybe_singleton_const(ultimate_rt), (nothrow, noub)) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 41fb774266f25..e2f2a1f2cc975 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -261,36 +261,37 @@ end function _typeinf(interp::AbstractInterpreter, frame::InferenceState) typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle # with no active ip's, frame is done - frames = frame.callers_in_cycle - if isempty(frames) - finish_nocycle(interp, frame) - elseif length(frames) == 1 - @assert frames[1] === frame "invalid callers_in_cycle" + frames = frame.callstack::Vector{AbsIntState} + if length(frames) == frame.cycleid finish_nocycle(interp, frame) else - finish_cycle(interp, frames) + @assert frame.cycleid != 0 + finish_cycle(interp, frames, frame.cycleid) end - empty!(frames) return true end function finish_nocycle(::AbstractInterpreter, frame::InferenceState) - frame.dont_work_on_me = true finishinfer!(frame, frame.interp) opt = frame.result.src if opt isa OptimizationState # implies `may_optimize(caller.interp) === true` optimize(frame.interp, opt, frame.result) end finish!(frame.interp, frame) + if frame.cycleid != 0 + frames = frame.callstack::Vector{AbsIntState} + @assert frames[end] === frame + pop!(frames) + end return nothing end -function finish_cycle(::AbstractInterpreter, frames::Vector{InferenceState}) +function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cycleid::Int) cycle_valid_worlds = WorldRange() cycle_valid_effects = EFFECTS_TOTAL - for caller in frames - @assert !(caller.dont_work_on_me) - caller.dont_work_on_me = true + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState + @assert caller.cycleid == cycleid # converge the world age range and effects for this cycle here: # all frames in the cycle should have the same bits of `valid_worlds` and `effects` # that are simply the intersection of each partial computation, without having @@ -298,19 +299,23 @@ function finish_cycle(::AbstractInterpreter, frames::Vector{InferenceState}) cycle_valid_worlds = intersect(cycle_valid_worlds, caller.valid_worlds) cycle_valid_effects = merge_effects(cycle_valid_effects, caller.ipo_effects) end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState adjust_cycle_frame!(caller, cycle_valid_worlds, cycle_valid_effects) finishinfer!(caller, caller.interp) end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState opt = caller.result.src if opt isa OptimizationState # implies `may_optimize(caller.interp) === true` optimize(caller.interp, opt, caller.result) end end - for caller in frames + for caller in cycleid:length(frames) + caller = frames[caller]::InferenceState finish!(caller.interp, caller) end + resize!(frames, cycleid - 1) return nothing end @@ -396,9 +401,9 @@ end function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) if typ isa LimitedAccuracy - if sv.parent === nothing + if sv.parentid === 0 # we might have introduced a limit marker, but we should know it must be sv and other callers_in_cycle - #@assert !isempty(sv.callers_in_cycle) + #@assert !isempty(callers_in_cycle(sv)) # FIXME: this assert fails, appearing to indicate there is a bug in filtering this list earlier. # In particular (during doctests for example), during inference of # show(Base.IOContext{Base.GenericIOBuffer{Memory{UInt8}}}, Base.Multimedia.MIME{:var"text/plain"}, LinearAlgebra.BunchKaufman{Float64, Array{Float64, 2}, Array{Int64, 1}}) @@ -407,7 +412,7 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) end causes = copy(typ.causes) delete!(causes, sv) - for caller in sv.callers_in_cycle + for caller in callers_in_cycle(sv) delete!(causes, caller) end if isempty(causes) @@ -521,6 +526,7 @@ end # update the MethodInstance function finishinfer!(me::InferenceState, interp::AbstractInterpreter) # prepare to run optimization passes on fulltree + @assert isempty(me.ip) s_edges = get_stmt_edges!(me, 1) for i = 2:length(me.stmt_edges) isassigned(me.stmt_edges, i) || continue @@ -541,7 +547,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) gt = me.ssavaluetypes for j = 1:length(gt) gt[j] = gtj = cycle_fix_limited(gt[j], me) - if gtj isa LimitedAccuracy && me.parent !== nothing + if gtj isa LimitedAccuracy && me.parentid != 0 limited_src = true break end @@ -573,10 +579,10 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) type_annotate!(interp, me) mayopt = may_optimize(interp) doopt = mayopt && - # disable optimization if we don't use this later - (me.cache_mode != CACHE_MODE_NULL || me.parent !== nothing) && + # disable optimization if we don't use this later (because it is not cached) + me.cache_mode != CACHE_MODE_NULL && # disable optimization if we've already obtained very accurate result - !result_is_constabi(interp, result, mayopt) + !result_is_constabi(interp, result) if doopt result.src = OptimizationState(me, interp) else @@ -746,41 +752,29 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState) return nothing end -# at the end, all items in b's cycle -# will now be added to a's cycle -function union_caller_cycle!(a::InferenceState, b::InferenceState) - callers_in_cycle = b.callers_in_cycle - b.parent = a.parent - b.callers_in_cycle = a.callers_in_cycle - contains_is(a.callers_in_cycle, b) || push!(a.callers_in_cycle, b) - if callers_in_cycle !== a.callers_in_cycle - for caller in callers_in_cycle - if caller !== b - caller.parent = a.parent - caller.callers_in_cycle = a.callers_in_cycle - push!(a.callers_in_cycle, caller) - end - end - end - return -end - -function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, ancestor::InferenceState, child::InferenceState) +function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, child::InferenceState) # add backedge of parent <- child # then add all backedges of parent <- parent.parent - # and merge all of the callers into ancestor.callers_in_cycle - # and ensure that walking the parent list will get the same result (DAG) from everywhere + frames = parent.callstack::Vector{AbsIntState} + @assert child.callstack === frames + ancestorid = child.cycleid while true add_cycle_backedge!(parent, child) - union_caller_cycle!(ancestor, child) + parent.cycleid === ancestorid && break child = parent - child === ancestor && break parent = frame_parent(child) while !isa(parent, InferenceState) # XXX we may miss some edges here? parent = frame_parent(parent::IRInterpretationState) end - parent = parent::InferenceState + end + # ensure that walking the callstack has the same cycleid (DAG) + for frame = reverse(ancestorid:length(frames)) + frame = frames[frame] + frame isa InferenceState || continue + frame.cycleid == ancestorid && break + @assert frame.cycleid > ancestorid + frame.cycleid = ancestorid end end @@ -796,8 +790,8 @@ end # Walk through `mi`'s upstream call chain, starting at `parent`. If a parent # frame matching `mi` is encountered, then there is a cycle in the call graph # (i.e. `mi` is a descendant callee of itself). Upon encountering this cycle, -# we "resolve" it by merging the call chain, which entails unioning each intermediary -# frame's `callers_in_cycle` field and adding the appropriate backedges. Finally, +# we "resolve" it by merging the call chain, which entails updating each intermediary +# frame's `cycleid` field and adding the appropriate backedges. Finally, # we return `mi`'s pre-existing frame. If no cycles are found, `nothing` is # returned instead. function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, parent::AbsIntState) @@ -805,10 +799,11 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa # This works just because currently the `:terminate` condition guarantees that # irinterp doesn't fail into unresolved cycles, but it's not a good solution. # We should revisit this once we have a better story for handling cycles in irinterp. - isa(parent, InferenceState) || return false - frame = parent + frames = parent.callstack::Vector{AbsIntState} uncached = false - while isa(frame, InferenceState) + for frame = reverse(1:length(frames)) + frame = frames[frame] + isa(frame, InferenceState) || break uncached |= !is_cached(frame) # ensure we never add an uncached frame to a cycle if is_same_frame(interp, mi, frame) if uncached @@ -818,20 +813,9 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa poison_callstack!(parent, frame) return true end - merge_call_chain!(interp, parent, frame, frame) + merge_call_chain!(interp, parent, frame) return frame end - for caller in callers_in_cycle(frame) - if is_same_frame(interp, mi, caller) - if uncached - poison_callstack!(parent, frame) - return true - end - merge_call_chain!(interp, parent, frame, caller) - return caller - end - end - frame = frame_parent(frame) end return false end @@ -920,9 +904,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize end return EdgeCallResult(Any, Any, nothing, Effects()) end - if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution - frame.parent = caller - end + assign_parentchild!(frame, caller) typeinf(interp, frame) update_valid_age!(caller, frame.valid_worlds) isinferred = is_inferred(frame) @@ -1011,9 +993,8 @@ function codeinstance_for_const_with_code(interp::AbstractInterpreter, code::Cod code.relocatability, src.debuginfo) end -result_is_constabi(interp::AbstractInterpreter, result::InferenceResult, - run_optimizer::Bool=may_optimize(interp)) = - run_optimizer && may_discard_trees(interp) && is_result_constabi_eligible(result) +result_is_constabi(interp::AbstractInterpreter, result::InferenceResult) = + may_discard_trees(interp) && is_result_constabi_eligible(result) # compute an inferred AST and return type typeinf_code(interp::AbstractInterpreter, match::MethodMatch, run_optimizer::Bool) = @@ -1024,11 +1005,6 @@ typeinf_code(interp::AbstractInterpreter, method::Method, @nospecialize(atype), function typeinf_code(interp::AbstractInterpreter, mi::MethodInstance, run_optimizer::Bool) frame = typeinf_frame(interp, mi, run_optimizer) frame === nothing && return nothing - is_inferred(frame) || return nothing - if result_is_constabi(interp, frame.result, run_optimizer) - rt = frame.result.result::Const - return codeinfo_for_const(interp, frame.linfo, rt.val) - end return frame.src end @@ -1051,17 +1027,14 @@ typeinf_ircode(interp::AbstractInterpreter, method::Method, @nospecialize(atype) typeinf_ircode(interp, specialize_method(method, atype, sparams), optimize_until) function typeinf_ircode(interp::AbstractInterpreter, mi::MethodInstance, optimize_until::Union{Integer,AbstractString,Nothing}) - start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) frame = typeinf_frame(interp, mi, false) if frame === nothing - ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) return nothing, Any end (; result) = frame opt = OptimizationState(frame, interp) ir = run_passes_ipo_safe(opt.src, opt, result, optimize_until) rt = widenconst(ignorelimited(result.result)) - ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) return ir, rt end @@ -1072,13 +1045,22 @@ typeinf_frame(interp::AbstractInterpreter, method::Method, @nospecialize(atype), run_optimizer::Bool) = typeinf_frame(interp, specialize_method(method, atype, sparams), run_optimizer) function typeinf_frame(interp::AbstractInterpreter, mi::MethodInstance, run_optimizer::Bool) - start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) result = InferenceResult(mi, typeinf_lattice(interp)) - cache_mode = run_optimizer ? :global : :no - frame = InferenceState(result, cache_mode, interp) + frame = InferenceState(result, #=cache_mode=#:no, interp) frame === nothing && return nothing typeinf(interp, frame) - ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + is_inferred(frame) || return nothing + if run_optimizer + if result_is_constabi(interp, frame.result) + rt = frame.result.result::Const + opt = codeinfo_for_const(interp, frame.linfo, rt.val) + else + opt = OptimizationState(frame, interp) + optimize(interp, opt, frame.result) + opt = ir_to_codeinf!(opt) + end + result.src = frame.src = opt + end return frame end diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index b000f353443c4..424564b70384c 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -394,13 +394,21 @@ let errf = tempname(), try redirect_stderr(new_stderr) @test occursin("f_broken_code", sprint(code_native, h_broken_code, ())) + Libc.flush_cstdio() println(new_stderr, "start") flush(new_stderr) - @test_throws "could not compile the specified method" sprint(code_native, f_broken_code, ()) + @test_throws "could not compile the specified method" sprint(io -> code_native(io, f_broken_code, (), dump_module=true)) Libc.flush_cstdio() - println(new_stderr, "end") + println(new_stderr, "middle") + flush(new_stderr) + @test !isempty(sprint(io -> code_native(io, f_broken_code, (), dump_module=false))) + Libc.flush_cstdio() + println(new_stderr, "later") flush(new_stderr) @test invokelatest(g_broken_code) == 0 + Libc.flush_cstdio() + println(new_stderr, "end") + flush(new_stderr) finally Libc.flush_cstdio() redirect_stderr(old_stderr) @@ -410,6 +418,14 @@ let errf = tempname(), Internal error: encountered unexpected error during compilation of f_broken_code: ErrorException(\"unsupported or misplaced expression \\\"invalid\\\" in function f_broken_code\") """) || errstr + @test occursin("""\nmiddle + Internal error: encountered unexpected error during compilation of f_broken_code: + ErrorException(\"unsupported or misplaced expression \\\"invalid\\\" in function f_broken_code\") + """, errstr) || errstr + @test occursin("""\nlater + Internal error: encountered unexpected error during compilation of f_broken_code: + ErrorException(\"unsupported or misplaced expression \\\"invalid\\\" in function f_broken_code\") + """, errstr) || errstr @test endswith(errstr, "\nend\n") || errstr end rm(errf) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index dc21cfe529e46..77f7fdf15cc9c 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -603,7 +603,7 @@ is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode function is_call_graph_uncached(sv::CC.InferenceState) CC.is_cached(sv) && return false - parent = sv.parent + parent = CC.frame_parent(sv) parent === nothing && return true return is_call_graph_uncached(parent::CC.InferenceState) end @@ -626,7 +626,7 @@ function is_repl_frame_getproperty(sv::CC.InferenceState) def isa Method || return false def.name === :getproperty || return false CC.is_cached(sv) && return false - return is_repl_frame(sv.parent) + return is_repl_frame(CC.frame_parent(sv)) end # aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame` From a2b1b4e335a9cc97e502369840fa1a47ed44bf2a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 20 Aug 2024 16:42:30 -0400 Subject: [PATCH 15/94] Reduce size of Task object (#55515) Move the registers onto the stack, so that they only are present when the Task is actually switched out, saving memory when the Task is not running yet or already finished. It makes this mostly just a huge renaming job. On Linux x86_64 this reduces it from 376 bytes to 184 bytes. Has some additional advantages too, such as copy_stack tasks (e.g. with always_copy_stacks) can migrate to other threads before starting if they are not sticky. Also fixes a variable that got mixed up by #54639 and caused always_copy_stacks to abort, since the stack limits were wrong. Also now fixes https://github.com/JuliaLang/julia/issues/43124, though I am not quite confident enough in it to re-enable that test right now. --- src/gc-debug.c | 8 +- src/gc-stacks.c | 22 +- src/gc-stock.c | 10 +- src/init.c | 21 +- src/julia.h | 6 +- src/julia_internal.h | 3 +- src/julia_threads.h | 14 +- src/signals-unix.c | 6 +- src/stackwalk.c | 65 +++--- src/task.c | 503 +++++++++++++++++++++++-------------------- 10 files changed, 342 insertions(+), 316 deletions(-) diff --git a/src/gc-debug.c b/src/gc-debug.c index ec3c8d731edd8..19dd93af5f236 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -537,13 +537,13 @@ static void gc_scrub_task(jl_task_t *ta) char *low; char *high; - if (ta->copy_stack && ptls2 && ta == jl_atomic_load_relaxed(&ptls2->current_task)) { + if (ta->ctx.copy_stack && ptls2 && ta == jl_atomic_load_relaxed(&ptls2->current_task)) { low = (char*)ptls2->stackbase - ptls2->stacksize; high = (char*)ptls2->stackbase; } - else if (ta->stkbuf) { - low = (char*)ta->stkbuf; - high = (char*)ta->stkbuf + ta->bufsz; + else if (ta->ctx.stkbuf) { + low = (char*)ta->ctx.stkbuf; + high = (char*)ta->ctx.stkbuf + ta->ctx.bufsz; } else return; diff --git a/src/gc-stacks.c b/src/gc-stacks.c index 5706f4ce67c1d..65173d3a8df37 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -186,14 +186,14 @@ JL_DLLEXPORT void jl_free_stack(void *stkbuf, size_t bufsz) void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task) { // avoid adding an original thread stack to the free list - if (task == ptls->root_task && !task->copy_stack) + if (task == ptls->root_task && !task->ctx.copy_stack) return; - void *stkbuf = task->stkbuf; - size_t bufsz = task->bufsz; + void *stkbuf = task->ctx.stkbuf; + size_t bufsz = task->ctx.bufsz; if (bufsz <= pool_sizes[JL_N_STACK_POOLS - 1]) { unsigned pool_id = select_pool(bufsz); if (pool_sizes[pool_id] == bufsz) { - task->stkbuf = NULL; + task->ctx.stkbuf = NULL; #ifdef _COMPILER_ASAN_ENABLED_ __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); #endif @@ -296,17 +296,17 @@ void sweep_stack_pools(void) JL_NOTSAFEPOINT jl_task_t *t = (jl_task_t*)lst[n]; assert(jl_is_task(t)); if (gc_marked(jl_astaggedvalue(t)->bits.gc)) { - if (t->stkbuf == NULL) + if (t->ctx.stkbuf == NULL) ndel++; // jl_release_task_stack called else n++; } else { ndel++; - void *stkbuf = t->stkbuf; - size_t bufsz = t->bufsz; + void *stkbuf = t->ctx.stkbuf; + size_t bufsz = t->ctx.bufsz; if (stkbuf) { - t->stkbuf = NULL; + t->ctx.stkbuf = NULL; _jl_free_stack(ptls2, stkbuf, bufsz); } #ifdef _COMPILER_TSAN_ENABLED_ @@ -338,7 +338,7 @@ JL_DLLEXPORT jl_array_t *jl_live_tasks(void) continue; small_arraylist_t *live_tasks = &ptls2->gc_tls.heap.live_tasks; size_t n = mtarraylist_length(live_tasks); - l += n + (ptls2->root_task->stkbuf != NULL); + l += n + (ptls2->root_task->ctx.stkbuf != NULL); } l += l / 20; // add 5% for margin of estimation error jl_array_t *a = jl_alloc_vec_any(l); // may gc, changing the number of tasks and forcing us to reload everything @@ -350,7 +350,7 @@ JL_DLLEXPORT jl_array_t *jl_live_tasks(void) if (ptls2 == NULL) continue; jl_task_t *t = ptls2->root_task; - if (t->stkbuf != NULL) { + if (t->ctx.stkbuf != NULL) { if (j == l) goto restart; jl_array_data(a,void*)[j++] = t; @@ -359,7 +359,7 @@ JL_DLLEXPORT jl_array_t *jl_live_tasks(void) size_t n = mtarraylist_length(live_tasks); for (size_t i = 0; i < n; i++) { jl_task_t *t = (jl_task_t*)mtarraylist_get(live_tasks, i); - if (t->stkbuf != NULL) { + if (t->ctx.stkbuf != NULL) { if (j == l) goto restart; jl_array_data(a,void*)[j++] = t; diff --git a/src/gc-stock.c b/src/gc-stock.c index 1fc2087da6503..3ae14f378a2e7 100644 --- a/src/gc-stock.c +++ b/src/gc-stock.c @@ -2144,9 +2144,9 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ (ta, tid != -1 && ta == gc_all_tls_states[tid]->root_task)); } #ifdef COPY_STACKS - void *stkbuf = ta->stkbuf; - if (stkbuf && ta->copy_stack) { - gc_setmark_buf_(ptls, stkbuf, bits, ta->bufsz); + void *stkbuf = ta->ctx.stkbuf; + if (stkbuf && ta->ctx.copy_stack) { + gc_setmark_buf_(ptls, stkbuf, bits, ta->ctx.bufsz); // For gc_heap_snapshot_record: // TODO: attribute size of stack // TODO: edge to stack data @@ -2159,12 +2159,12 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ uintptr_t lb = 0; uintptr_t ub = (uintptr_t)-1; #ifdef COPY_STACKS - if (stkbuf && ta->copy_stack && !ta->ptls) { + if (stkbuf && ta->ctx.copy_stack && !ta->ptls) { int16_t tid = jl_atomic_load_relaxed(&ta->tid); assert(tid >= 0); jl_ptls_t ptls2 = gc_all_tls_states[tid]; ub = (uintptr_t)ptls2->stackbase; - lb = ub - ta->copy_stack; + lb = ub - ta->ctx.copy_stack; offset = (uintptr_t)stkbuf - lb; } #endif diff --git a/src/init.c b/src/init.c index eff786b564e54..9e6a695c71eb0 100644 --- a/src/init.c +++ b/src/init.c @@ -64,15 +64,19 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) // threads since it seems to return bogus values for master thread on Linux // and possibly OSX. if (!ismaster) { -# if defined(_OS_LINUX_) +# if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) pthread_attr_t attr; +#if defined(_OS_FREEBSD_) + pthread_attr_get_np(pthread_self(), &attr); +#else pthread_getattr_np(pthread_self(), &attr); +#endif void *stackaddr; size_t stacksize; pthread_attr_getstack(&attr, &stackaddr, &stacksize); pthread_attr_destroy(&attr); - *stack_hi = stackaddr; - *stack_lo = (char*)stackaddr - stacksize; + *stack_lo = stackaddr; + *stack_hi = (char*)stackaddr + stacksize; return; # elif defined(_OS_DARWIN_) extern void *pthread_get_stackaddr_np(pthread_t thread); @@ -80,19 +84,8 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) pthread_t thread = pthread_self(); void *stackaddr = pthread_get_stackaddr_np(thread); size_t stacksize = pthread_get_stacksize_np(thread); - *stack_hi = stackaddr; *stack_lo = (char*)stackaddr - stacksize; - return; -# elif defined(_OS_FREEBSD_) - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - void *stackaddr; - size_t stacksize; - pthread_attr_getstack(&attr, &stackaddr, &stacksize); - pthread_attr_destroy(&attr); *stack_hi = stackaddr; - *stack_lo = (char*)stackaddr - stacksize; return; # else # warning "Getting precise stack size for thread is not supported." diff --git a/src/julia.h b/src/julia.h index e211f31c6512c..074c50fd0aa21 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2231,11 +2231,7 @@ typedef struct _jl_task_t { // current exception handler jl_handler_t *eh; // saved thread state - jl_ucontext_t ctx; - void *stkbuf; // malloc'd memory (either copybuf or stack) - size_t bufsz; // actual sizeof stkbuf - unsigned int copy_stack:31; // sizeof stack for copybuf - unsigned int started:1; + jl_ucontext_t ctx; // pointer into stkbuf, if suspended } jl_task_t; #define JL_TASK_STATE_RUNNABLE 0 diff --git a/src/julia_internal.h b/src/julia_internal.h index d4d1a3239785c..23a9c90edf8aa 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -65,7 +65,8 @@ static inline void asan_unpoison_task_stack(jl_task_t *ct, jl_jmp_buf *buf) that we're resetting to. The idea is to remove the poison from the frames that we're skipping over, since they won't be unwound. */ uintptr_t top = jmpbuf_sp(buf); - uintptr_t bottom = (uintptr_t)ct->stkbuf; + uintptr_t bottom = (uintptr_t)(ct->ctx.copy_stack ? (char*)ct->ptls->stackbase - ct->ptls->stacksize : (char*)ct->ctx.stkbuf); + //uintptr_t bottom = (uintptr_t)⊤ __asan_unpoison_stack_memory(bottom, top - bottom); } static inline void asan_unpoison_stack_memory(uintptr_t addr, size_t size) { diff --git a/src/julia_threads.h b/src/julia_threads.h index 9a2a8cec375f5..e56ff5edd6176 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -86,9 +86,13 @@ typedef ucontext_t _jl_ucontext_t; typedef struct { union { - _jl_ucontext_t ctx; - jl_stack_context_t copy_ctx; + _jl_ucontext_t *ctx; + jl_stack_context_t *copy_ctx; }; + void *stkbuf; // malloc'd memory (either copybuf or stack) + size_t bufsz; // actual sizeof stkbuf + unsigned int copy_stack:31; // sizeof stack for copybuf + unsigned int started:1; #if defined(_COMPILER_TSAN_ENABLED_) void *tsan_state; #endif @@ -155,13 +159,9 @@ typedef struct _jl_tls_states_t { struct _jl_task_t *previous_task; struct _jl_task_t *root_task; struct _jl_timing_block_t *timing_stack; + // This is the location of our copy_stack void *stackbase; size_t stacksize; - union { - _jl_ucontext_t base_ctx; // base context of stack - // This hack is needed to support always_copy_stacks: - jl_stack_context_t copy_stack_ctx; - }; // Temp storage for exception thrown in signal handler. Not rooted. struct _jl_value_t *sig_exception; // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. diff --git a/src/signals-unix.c b/src/signals-unix.c index edca523bed6d1..005422bea03d3 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -230,13 +230,13 @@ static pthread_t signals_thread; static int is_addr_on_stack(jl_task_t *ct, void *addr) { - if (ct->copy_stack) { + if (ct->ctx.copy_stack) { jl_ptls_t ptls = ct->ptls; return ((char*)addr > (char*)ptls->stackbase - ptls->stacksize && (char*)addr < (char*)ptls->stackbase); } - return ((char*)addr > (char*)ct->stkbuf && - (char*)addr < (char*)ct->stkbuf + ct->bufsz); + return ((char*)addr > (char*)ct->ctx.stkbuf && + (char*)addr < (char*)ct->ctx.stkbuf + ct->ctx.bufsz); } static void sigdie_handler(int sig, siginfo_t *info, void *context) diff --git a/src/stackwalk.c b/src/stackwalk.c index a63694e7c3b0c..15a9fddeac9a4 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -83,7 +83,7 @@ static int jl_unw_stepn(bt_cursor_t *cursor, jl_bt_element_t *bt_data, size_t *b skip--; } #endif -#if !defined(_OS_WINDOWS_) +#if !defined(_OS_WINDOWS_) // no point on windows, since RtlVirtualUnwind won't give us a second chance if the segfault happens in ntdll jl_jmp_buf *old_buf = jl_get_safe_restore(); jl_jmp_buf buf; jl_set_safe_restore(&buf); @@ -921,14 +921,12 @@ _os_ptr_munge(uintptr_t ptr) JL_NOTSAFEPOINT extern bt_context_t *jl_to_bt_context(void *sigctx); -static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT +JL_DLLEXPORT size_t jl_record_backtrace(jl_task_t *t, jl_bt_element_t *bt_data, size_t max_bt_size) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; - ptls->bt_size = 0; if (t == ct) { - ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE, 0); - return; + return rec_backtrace(bt_data, max_bt_size, 0); } bt_context_t *context = NULL; bt_context_t c; @@ -936,9 +934,11 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT while (!jl_atomic_cmpswap(&t->tid, &old, ptls->tid) && old != ptls->tid) { int lockret = jl_lock_stackwalk(); // if this task is already running somewhere, we need to stop the thread it is running on and query its state - if (!jl_thread_suspend_and_get_state(old, 0, &c)) { + if (!jl_thread_suspend_and_get_state(old, 1, &c)) { jl_unlock_stackwalk(lockret); - return; + if (jl_atomic_load_relaxed(&t->tid) != old) + continue; + return 0; } jl_unlock_stackwalk(lockret); if (jl_atomic_load_relaxed(&t->tid) == old) { @@ -953,11 +953,11 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT // got the wrong thread stopped, try again jl_thread_resume(old); } - if (context == NULL && (!t->copy_stack && t->started && t->stkbuf != NULL)) { + if (context == NULL && (!t->ctx.copy_stack && t->ctx.started && t->ctx.ctx != NULL)) { // need to read the context from the task stored state #if defined(_OS_WINDOWS_) memset(&c, 0, sizeof(c)); - _JUMP_BUFFER *mctx = (_JUMP_BUFFER*)&t->ctx.ctx.uc_mcontext; + _JUMP_BUFFER *mctx = (_JUMP_BUFFER*)&t->ctx.ctx->uc_mcontext; #if defined(_CPU_X86_64_) c.Rbx = mctx->Rbx; c.Rsp = mctx->Rsp; @@ -979,13 +979,13 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT #endif context = &c; #elif defined(JL_HAVE_UNW_CONTEXT) - context = &t->ctx.ctx; + context = t->ctx.ctx; #elif defined(JL_HAVE_UCONTEXT) - context = jl_to_bt_context(&t->ctx.ctx); + context = jl_to_bt_context(t->ctx.ctx); #elif defined(JL_HAVE_ASM) memset(&c, 0, sizeof(c)); #if defined(_OS_LINUX_) && defined(__GLIBC__) - __jmp_buf *mctx = &t->ctx.ctx.uc_mcontext->__jmpbuf; + __jmp_buf *mctx = &t->ctx.ctx->uc_mcontext->__jmpbuf; mcontext_t *mc = &c.uc_mcontext; #if defined(_CPU_X86_) // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S @@ -1071,13 +1071,13 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT mc->pc = mc->regs[30]; context = &c; #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown linux") + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown linux") (void)mc; (void)c; (void)mctx; #endif #elif defined(_OS_DARWIN_) - sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + sigjmp_buf *mctx = &t->ctx.ctx->uc_mcontext; #if defined(_CPU_X86_64_) // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s x86_thread_state64_t *mc = (x86_thread_state64_t*)&c; @@ -1133,12 +1133,12 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT mc->__pad = 0; // aka __ra_sign_state = not signed context = &c; #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown darwin") + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown darwin") (void)mctx; (void)c; #endif #elif defined(_OS_FREEBSD_) - sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + sigjmp_buf *mctx = &t->ctx.ctx->uc_mcontext; mcontext_t *mc = &c.uc_mcontext; #if defined(_CPU_X86_64_) // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S @@ -1175,24 +1175,26 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT mc->mc_fpregs.fp_q[14] = ((long*)mctx)[20]; context = &c; #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown freebsd") + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown freebsd") (void)mctx; (void)c; #endif #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown system") + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown system") (void)c; #endif #else - #pragma message("jl_rec_backtrace not defined for unknown task system") + #pragma message("jl_record_backtrace not defined for unknown task system") #endif } + size_t bt_size = 0; if (context) - ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, context, t->gcstack); + bt_size = rec_backtrace_ctx(bt_data, max_bt_size, context, t->gcstack); if (old == -1) jl_atomic_store_relaxed(&t->tid, old); else if (old != ptls->tid) jl_thread_resume(old); + return bt_size; } //-------------------------------------------------- @@ -1224,12 +1226,15 @@ JL_DLLEXPORT void jlbacktracet(jl_task_t *t) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; - jl_rec_backtrace(t); - size_t i, bt_size = ptls->bt_size; + ptls->bt_size = 0; jl_bt_element_t *bt_data = ptls->bt_data; + size_t bt_size = jl_record_backtrace(t, bt_data, JL_MAX_BT_SIZE); + size_t i; for (i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { jl_print_bt_entry_codeloc(bt_data + i); } + if (bt_size == 0) + jl_safe_printf(" no backtrace recorded\n"); } JL_DLLEXPORT void jl_print_backtrace(void) JL_NOTSAFEPOINT @@ -1269,14 +1274,9 @@ JL_DLLEXPORT void jl_print_task_backtraces(int show_done) JL_NOTSAFEPOINT jl_safe_printf(" ---- Root task (%p)\n", ptls2->root_task); if (t != NULL) { jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", - t->sticky, t->started, t_state, + t->sticky, t->ctx.started, t_state, jl_atomic_load_relaxed(&t->tid) + 1); - if (t->stkbuf != NULL) { - jlbacktracet(t); - } - else { - jl_safe_printf(" no stack\n"); - } + jlbacktracet(t); } jl_safe_printf(" ---- End root task\n"); } @@ -1291,12 +1291,9 @@ JL_DLLEXPORT void jl_print_task_backtraces(int show_done) JL_NOTSAFEPOINT jl_safe_printf(" ---- Task %zu (%p)\n", j + 1, t); // n.b. this information might not be consistent with the stack printing after it, since it could start running or change tid, etc. jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", - t->sticky, t->started, t_state, + t->sticky, t->ctx.started, t_state, jl_atomic_load_relaxed(&t->tid) + 1); - if (t->stkbuf != NULL) - jlbacktracet(t); - else - jl_safe_printf(" no stack\n"); + jlbacktracet(t); jl_safe_printf(" ---- End task %zu\n", j + 1); } jl_safe_printf("==== End thread %d\n", ptls2->tid + 1); diff --git a/src/task.c b/src/task.c index 1f41ebd8cd2f8..a88f3b55fc419 100644 --- a/src/task.c +++ b/src/task.c @@ -49,27 +49,27 @@ extern "C" { // c.f. interceptor in jl_dlopen as well void (*real_siglongjmp)(jmp_buf _Buf, int _Value) = NULL; #endif -static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_task_t *from, jl_task_t *to) { +static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_ucontext_t *from, jl_ucontext_t *to) { if (to->copy_stack) - __sanitizer_start_switch_fiber(&from->ctx.asan_fake_stack, (char*)ptls->stackbase-ptls->stacksize, ptls->stacksize); + __sanitizer_start_switch_fiber(&from->asan_fake_stack, (char*)ptls->stackbase - ptls->stacksize, ptls->stacksize); else - __sanitizer_start_switch_fiber(&from->ctx.asan_fake_stack, to->stkbuf, to->bufsz); + __sanitizer_start_switch_fiber(&from->asan_fake_stack, to->stkbuf, to->bufsz); } -static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_task_t *to) { +static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_ucontext_t *to) { if (to->copy_stack) - __sanitizer_start_switch_fiber(NULL, (char*)ptls->stackbase-ptls->stacksize, ptls->stacksize); + __sanitizer_start_switch_fiber(NULL, (char*)ptls->stackbase - ptls->stacksize, ptls->stacksize); else __sanitizer_start_switch_fiber(NULL, to->stkbuf, to->bufsz); } -static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *current) { - __sanitizer_finish_switch_fiber(current->ctx.asan_fake_stack, NULL, NULL); +static inline void sanitizer_finish_switch_fiber(jl_ucontext_t *last, jl_ucontext_t *current) { + __sanitizer_finish_switch_fiber(current->asan_fake_stack, NULL, NULL); //(const void**)&last->stkbuf, //&last->bufsz); } #else -static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_task_t *from, jl_task_t *to) JL_NOTSAFEPOINT {} -static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_task_t *to) JL_NOTSAFEPOINT {} -static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *current) JL_NOTSAFEPOINT {} +static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_ucontext_t *from, jl_ucontext_t *to) JL_NOTSAFEPOINT {} +static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_ucontext_t *to) JL_NOTSAFEPOINT {} +static inline void sanitizer_finish_switch_fiber(jl_ucontext_t *last, jl_ucontext_t *current) JL_NOTSAFEPOINT {} #endif #if defined(_COMPILER_TSAN_ENABLED_) @@ -85,19 +85,6 @@ static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *cur jl_ucontext_t *_tsan_macro_ctx = (_ctx); \ __tsan_switch_to_fiber(_tsan_macro_ctx->tsan_state, 0); \ } while (0) -#ifdef COPY_STACKS -#define tsan_destroy_copyctx(_ptls, _ctx) do { \ - jl_ucontext_t *_tsan_macro_ctx = (_ctx); \ - if (_tsan_macro_ctx != &(_ptls)->root_task->ctx) { \ - __tsan_destroy_fiber(_tsan_macro_ctx->tsan_state); \ - } \ - _tsan_macro_ctx->tsan_state = NULL; \ - } while (0) -#define tsan_switch_to_copyctx(_ctx) do { \ - struct jl_stack_context_t *_tsan_macro_ctx = (_ctx); \ - __tsan_switch_to_fiber(_tsan_macro_ctx->tsan_state, 0); \ - } while (0) -#endif #else // just do minimal type-checking on the arguments #define tsan_destroy_ctx(_ptls, _ctx) do { \ @@ -108,16 +95,6 @@ static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *cur jl_ucontext_t *_tsan_macro_ctx = (_ctx); \ (void)_tsan_macro_ctx; \ } while (0) -#ifdef COPY_STACKS -#define tsan_destroy_copyctx(_ptls, _ctx) do { \ - jl_ucontext_t *_tsan_macro_ctx = (_ctx); \ - (void)_tsan_macro_ctx; \ - } while (0) -#define tsan_switch_to_copyctx(_ctx) do { \ - jl_ucontext_t *_tsan_macro_ctx = (_ctx); \ - (void)_tsan_macro_ctx; \ - } while (0) -#endif #endif // empirically, jl_finish_task needs about 64k stack space to infer/run @@ -134,7 +111,6 @@ static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *cur #define ROOT_TASK_STACK_ADJUSTMENT 3000000 #endif -static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) JL_NOTSAFEPOINT; static void jl_set_fiber(jl_ucontext_t *t); static void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t); static void jl_start_fiber_swap(jl_ucontext_t *savet, jl_ucontext_t *t); @@ -214,17 +190,17 @@ static void NOINLINE save_stack(jl_ptls_t ptls, jl_task_t *lastt, jl_task_t **pt assert(stackbase > frame_addr); size_t nb = stackbase - frame_addr; void *buf; - if (lastt->bufsz < nb) { - asan_free_copy_stack(lastt->stkbuf, lastt->bufsz); + if (lastt->ctx.bufsz < nb) { + asan_free_copy_stack(lastt->ctx.stkbuf, lastt->ctx.bufsz); buf = (void*)jl_gc_alloc_buf(ptls, nb); - lastt->stkbuf = buf; - lastt->bufsz = nb; + lastt->ctx.stkbuf = buf; + lastt->ctx.bufsz = nb; } else { - buf = lastt->stkbuf; + buf = lastt->ctx.stkbuf; } *pt = NULL; // clear the gc-root for the target task before copying the stack for saving - lastt->copy_stack = nb; + lastt->ctx.copy_stack = nb; lastt->sticky = 1; memcpy_stack_a16((uint64_t*)buf, (uint64_t*)frame_addr, nb); // this task's stack could have been modified after @@ -233,58 +209,97 @@ static void NOINLINE save_stack(jl_ptls_t ptls, jl_task_t *lastt, jl_task_t **pt jl_gc_wb_back(lastt); } -JL_NO_ASAN static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, char *p) +JL_NO_ASAN static void NOINLINE JL_NORETURN restore_stack(jl_ucontext_t *t, jl_ptls_t ptls, char *p) { size_t nb = t->copy_stack; char *_x = (char*)ptls->stackbase - nb; if (!p) { // switch to a stackframe that's beyond the bounds of the last switch - p = _x; - if ((char*)&_x > _x) { - p = (char*)alloca((char*)&_x - _x); + p = _x - 4096; + if ((char*)&_x > p) { + p = (char*)alloca((char*)&_x - p); } restore_stack(t, ptls, p); // pass p to ensure the compiler can't tailcall this or avoid the alloca } void *_y = t->stkbuf; assert(_x != NULL && _y != NULL); +#if defined(_CPU_X86_) || defined(_CPU_X86_64_) + void *volatile *return_address = (void *volatile *)__builtin_frame_address(0) + 1; + assert(*return_address == __builtin_return_address(0)); + *return_address = NULL; +#else +#pragma message("warning: CFI_NORETURN not implemented for this platform, so profiling of copy_stacks may segfault in this build") +#endif +CFI_NORETURN memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe #if defined(_OS_WINDOWS_) - jl_setcontext(&t->ctx.copy_ctx); + jl_setcontext(t->copy_ctx); #else - jl_longjmp(t->ctx.copy_ctx.uc_mcontext, 1); + jl_longjmp(t->copy_ctx->uc_mcontext, 1); #endif abort(); // unreachable } -JL_NO_ASAN static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *lastt) +JL_NO_ASAN static void restore_stack2(jl_ucontext_t *t, jl_ptls_t ptls, jl_ucontext_t *lastt) { assert(t->copy_stack && !lastt->copy_stack); size_t nb = t->copy_stack; - char *_x = (char*)ptls->stackbase - nb; - void *_y = t->stkbuf; - assert(_x != NULL && _y != NULL); - memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe + if (nb > 1) { + char *_x = (char*)ptls->stackbase - nb; + void *_y = t->stkbuf; + assert(_x != NULL && _y != NULL); + memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); + } +#if defined(_OS_WINDOWS_) + // jl_swapcontext and setjmp are the same on Windows, so we can just use swapcontext directly + tsan_switch_to_ctx(t); + jl_swapcontext(lastt->ctx, t->copy_ctx); +#else #if defined(JL_HAVE_UNW_CONTEXT) volatile int returns = 0; - int r = unw_getcontext(&lastt->ctx.ctx); + int r = unw_getcontext(lastt->ctx); if (++returns == 2) // r is garbage after the first return return; if (r != 0 || returns != 1) abort(); -#elif defined(JL_HAVE_ASM) || defined(_OS_WINDOWS_) - if (jl_setjmp(lastt->ctx.copy_ctx.uc_mcontext, 0)) +#elif defined(JL_HAVE_ASM) + if (jl_setjmp(lastt->ctx->uc_mcontext, 0)) return; #else #error COPY_STACKS is incompatible with this platform #endif - tsan_switch_to_copyctx(&t->ctx); -#if defined(_OS_WINDOWS_) - jl_setcontext(&t->ctx.copy_ctx); + tsan_switch_to_ctx(t); + jl_longjmp(t->copy_ctx->uc_mcontext, 1); +#endif +} + +JL_NO_ASAN static void NOINLINE restore_stack3(jl_ucontext_t *t, jl_ptls_t ptls, char *p) +{ +#if !defined(JL_HAVE_ASM) + char *_x = (char*)ptls->stackbase; + if (!p) { + // switch to a stackframe that's well beyond the bounds of the next switch + p = _x - 4096; + if ((char*)&_x > p) { + p = (char*)alloca((char*)&_x - p); + } + restore_stack3(t, ptls, p); // pass p to ensure the compiler can't tailcall this or avoid the alloca + } +#endif +#if defined(_CPU_X86_) || defined(_CPU_X86_64_) + void *volatile *return_address = (void *volatile *)__builtin_frame_address(0) + 1; + assert(*return_address == __builtin_return_address(0)); + *return_address = NULL; #else - jl_longjmp(t->ctx.copy_ctx.uc_mcontext, 1); +#pragma message("warning: CFI_NORETURN not implemented for this platform, so profiling of copy_stacks may segfault in this build") #endif +CFI_NORETURN + tsan_switch_to_ctx(t); + jl_start_fiber_set(t); // (doesn't return) + abort(); } + #endif /* Rooted by the base module */ @@ -298,9 +313,9 @@ void JL_NORETURN jl_finish_task(jl_task_t *ct) jl_atomic_store_release(&ct->_state, JL_TASK_STATE_FAILED); else jl_atomic_store_release(&ct->_state, JL_TASK_STATE_DONE); - if (ct->copy_stack) { // early free of stkbuf - asan_free_copy_stack(ct->stkbuf, ct->bufsz); - ct->stkbuf = NULL; + if (ct->ctx.copy_stack) { // early free of stkbuf + asan_free_copy_stack(ct->ctx.stkbuf, ct->ctx.bufsz); + ct->ctx.stkbuf = NULL; } // ensure that state is cleared ct->ptls->in_finalizer = 0; @@ -344,33 +359,33 @@ JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *ptid if (ptls2) { *ptid = jl_atomic_load_relaxed(&task->tid); #ifdef COPY_STACKS - if (task->copy_stack) { + if (task->ctx.copy_stack) { *size = ptls2->stacksize; return (char *)ptls2->stackbase - *size; } #endif } - *size = task->bufsz - off; - return (void *)((char *)task->stkbuf + off); + *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) { - if (!task->started) { + if (!task->ctx.started) { *total_start = *active_start = 0; *total_end = *active_end = 0; return; } jl_ptls_t ptls2 = task->ptls; - if (task->copy_stack && ptls2) { + if (task->ctx.copy_stack && ptls2) { *total_start = *active_start = (char*)ptls2->stackbase - ptls2->stacksize; *total_end = *active_end = (char*)ptls2->stackbase; } - else if (task->stkbuf) { - *total_start = *active_start = (char*)task->stkbuf; + else if (task->ctx.stkbuf) { + *total_start = *active_start = (char*)task->ctx.stkbuf; #ifndef _OS_WINDOWS_ jl_ptls_t ptls0 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; if (ptls0->root_task == task) { @@ -383,12 +398,12 @@ JL_DLLEXPORT void jl_active_task_stack(jl_task_t *task, } #endif - *total_end = *active_end = (char*)task->stkbuf + task->bufsz; + *total_end = *active_end = (char*)task->ctx.stkbuf + task->ctx.bufsz; #ifdef COPY_STACKS // save_stack stores the stack of an inactive task in stkbuf, and the // actual number of used bytes in copy_stack. - if (task->copy_stack > 1) - *active_end = (char*)task->stkbuf + task->copy_stack; + if (task->ctx.copy_stack > 1) + *active_end = (char*)task->ctx.stkbuf + task->ctx.copy_stack; #endif } else { @@ -449,20 +464,16 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) #endif int killed = jl_atomic_load_relaxed(&lastt->_state) != JL_TASK_STATE_RUNNABLE; - if (!t->started && !t->copy_stack) { + if (!t->ctx.started && !t->ctx.copy_stack) { // may need to allocate the stack - if (t->stkbuf == NULL) { - t->stkbuf = jl_alloc_fiber(&t->ctx.ctx, &t->bufsz, t); - if (t->stkbuf == NULL) { + if (t->ctx.stkbuf == NULL) { + t->ctx.stkbuf = jl_malloc_stack(&t->ctx.bufsz, t); + if (t->ctx.stkbuf == NULL) { #ifdef COPY_STACKS // fall back to stack copying if mmap fails - t->copy_stack = 1; + t->ctx.copy_stack = 1; + t->ctx.bufsz = 0; t->sticky = 1; - t->bufsz = 0; - if (always_copy_stacks) - memcpy(&t->ctx.copy_ctx, &ptls->copy_stack_ctx, sizeof(t->ctx.copy_ctx)); - else - memcpy(&t->ctx.ctx, &ptls->base_ctx, sizeof(t->ctx.ctx)); #else jl_throw(jl_memory_exception); #endif @@ -470,28 +481,45 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) } } + union { + _jl_ucontext_t ctx; + jl_stack_context_t copy_ctx; + } lasttstate; + if (killed) { *pt = NULL; // can't fail after here: clear the gc-root for the target task now lastt->gcstack = NULL; lastt->eh = NULL; - if (!lastt->copy_stack && lastt->stkbuf) { + if (!lastt->ctx.copy_stack && lastt->ctx.stkbuf) { // early free of stkbuf back to the pool jl_release_task_stack(ptls, lastt); } } else { + if (lastt->ctx.copy_stack) { // save the old copy-stack +#ifdef _OS_WINDOWS_ + lasttstate.copy_ctx.uc_stack.ss_sp = (char*)ptls->stackbase - ptls->stacksize; + lasttstate.copy_ctx.uc_stack.ss_size = ptls->stacksize; +#endif #ifdef COPY_STACKS - if (lastt->copy_stack) { // save the old copy-stack - save_stack(ptls, lastt, pt); // allocates (gc-safepoint, and can also fail) - if (jl_setjmp(lastt->ctx.copy_ctx.uc_mcontext, 0)) { - sanitizer_finish_switch_fiber(ptls->previous_task, jl_atomic_load_relaxed(&ptls->current_task)); - // TODO: mutex unlock the thread we just switched from + if (jl_setjmp(lasttstate.copy_ctx.uc_mcontext, 0)) { +#ifdef MIGRATE_TASKS + ptls = lastt->ptls; +#endif + lastt->ctx.copy_ctx = NULL; + sanitizer_finish_switch_fiber(&ptls->previous_task->ctx, &lastt->ctx); return; } - } - else + save_stack(ptls, lastt, pt); // allocates (gc-safepoint, and can also fail) + lastt->ctx.copy_ctx = &lasttstate.copy_ctx; +#else + abort(); #endif - *pt = NULL; // can't fail after here: clear the gc-root for the target task now + } + else { + *pt = NULL; // can't fail after here: clear the gc-root for the target task now + lastt->ctx.ctx = &lasttstate.ctx; + } } // set up global state for new task and clear global state for old task @@ -506,41 +534,44 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) ptls->previous_task = lastt; #endif - if (t->started) { + if (t->ctx.started) { + if (t->ctx.copy_stack) { #ifdef COPY_STACKS - if (t->copy_stack) { - if (lastt->copy_stack) { + if (lastt->ctx.copy_stack) { // Switching from copystack to copystack. Clear any shadow stack // memory above the saved shadow stack. - uintptr_t stacktop = (uintptr_t)ptls->stackbase - t->copy_stack; + uintptr_t stacktop = (uintptr_t)ptls->stackbase - t->ctx.copy_stack; uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); if (stackbottom < stacktop) - asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + asan_unpoison_stack_memory(stackbottom, stacktop - stackbottom); } - if (!killed && !lastt->copy_stack) { - sanitizer_start_switch_fiber(ptls, lastt, t); - restore_stack2(t, ptls, lastt); - } else { - tsan_switch_to_copyctx(&t->ctx); + if (!killed && !lastt->ctx.copy_stack) { + sanitizer_start_switch_fiber(ptls, &lastt->ctx, &t->ctx); + restore_stack2(&t->ctx, ptls, &lastt->ctx); // half jl_swap_fiber and half restore_stack + } + else { + tsan_switch_to_ctx(&t->ctx); if (killed) { - sanitizer_start_switch_fiber_killed(ptls, t); - tsan_destroy_copyctx(ptls, &lastt->ctx); - } else { - sanitizer_start_switch_fiber(ptls, lastt, t); + sanitizer_start_switch_fiber_killed(ptls, &t->ctx); + tsan_destroy_ctx(ptls, &lastt->ctx); + } + else { + sanitizer_start_switch_fiber(ptls, &lastt->ctx, &t->ctx); } - if (lastt->copy_stack) { - restore_stack(t, ptls, NULL); // (doesn't return) + if (lastt->ctx.copy_stack) { + restore_stack(&t->ctx, ptls, NULL); // (doesn't return) + abort(); } else { - restore_stack(t, ptls, (char*)1); // (doesn't return) + restore_stack(&t->ctx, ptls, (char*)1); // (doesn't return) + abort(); } } - } - else #endif - { - if (lastt->copy_stack) { + } + else { + if (lastt->ctx.copy_stack) { // Switching away from a copystack to a non-copystack. Clear // the whole shadow stack now, because otherwise we won't know // how much stack memory to clear the next time we switch to @@ -549,22 +580,23 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); // We're not restoring the stack, but we still need to unpoison the // stack, so it starts with a pristine stack. - asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + asan_unpoison_stack_memory(stackbottom, stacktop - stackbottom); } if (killed) { - sanitizer_start_switch_fiber_killed(ptls, t); + sanitizer_start_switch_fiber_killed(ptls, &t->ctx); tsan_switch_to_ctx(&t->ctx); tsan_destroy_ctx(ptls, &lastt->ctx); jl_set_fiber(&t->ctx); // (doesn't return) abort(); // unreachable } else { - sanitizer_start_switch_fiber(ptls, lastt, t); - if (lastt->copy_stack) { + sanitizer_start_switch_fiber(ptls, &lastt->ctx, &t->ctx); + if (lastt->ctx.copy_stack) { // Resume at the jl_setjmp earlier in this function, // don't do a full task swap tsan_switch_to_ctx(&t->ctx); jl_set_fiber(&t->ctx); // (doesn't return) + abort(); } else { jl_swap_fiber(&lastt->ctx, &t->ctx); @@ -573,41 +605,58 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) } } else { - if (lastt->copy_stack) { +#ifdef _COMPILER_TSAN_ENABLED_ + t->ctx.tsan_state = __tsan_create_fiber(0); +#endif + if (lastt->ctx.copy_stack) { uintptr_t stacktop = (uintptr_t)ptls->stackbase; uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); // We're not restoring the stack, but we still need to unpoison the // stack, so it starts with a pristine stack. - asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + asan_unpoison_stack_memory(stackbottom, stacktop - stackbottom); } - if (t->copy_stack && always_copy_stacks) { + if (t->ctx.copy_stack) { +#ifdef COPY_STACKS tsan_switch_to_ctx(&t->ctx); + // create a temporary non-copy_stack context for starting this fiber + jl_ucontext_t ctx = t->ctx; + ctx.ctx = NULL; + ctx.stkbuf = (char*)ptls->stackbase - ptls->stacksize; + ctx.bufsz = ptls->stacksize; + ctx.copy_stack = 0; + ctx.started = 0; if (killed) { - sanitizer_start_switch_fiber_killed(ptls, t); + sanitizer_start_switch_fiber_killed(ptls, &t->ctx); tsan_destroy_ctx(ptls, &lastt->ctx); - } else { - sanitizer_start_switch_fiber(ptls, lastt, t); + if (lastt->ctx.copy_stack) + restore_stack3(&ctx, ptls, NULL); // (doesn't return) + else + jl_start_fiber_set(&ctx); + abort(); + } + sanitizer_start_switch_fiber(ptls, &lastt->ctx, &t->ctx); + if (lastt->ctx.copy_stack) { + restore_stack3(&ctx, ptls, NULL); // (doesn't return) + abort(); + } + else { + jl_start_fiber_swap(&lastt->ctx, &ctx); } -#ifdef COPY_STACKS -#if defined(_OS_WINDOWS_) - jl_setcontext(&t->ctx.copy_ctx); #else - jl_longjmp(t->ctx.copy_ctx.uc_mcontext, 1); + abort(); #endif -#endif - abort(); // unreachable } else { if (killed) { - sanitizer_start_switch_fiber_killed(ptls, t); + sanitizer_start_switch_fiber_killed(ptls, &t->ctx); tsan_switch_to_ctx(&t->ctx); tsan_destroy_ctx(ptls, &lastt->ctx); jl_start_fiber_set(&t->ctx); // (doesn't return) abort(); } - sanitizer_start_switch_fiber(ptls, lastt, t); - if (lastt->copy_stack) { - // Resume at the jl_setjmp earlier in this function + sanitizer_start_switch_fiber(ptls, &lastt->ctx, &t->ctx); + if (lastt->ctx.copy_stack) { + // copy_stack resumes at the jl_setjmp earlier in this function, so don't swap here tsan_switch_to_ctx(&t->ctx); jl_start_fiber_set(&t->ctx); // (doesn't return) abort(); @@ -617,7 +666,14 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) } } } - sanitizer_finish_switch_fiber(ptls->previous_task, jl_atomic_load_relaxed(&ptls->current_task)); + +#ifdef MIGRATE_TASKS + ptls = lastt->ptls; +#endif + assert(ptls); + assert(lastt == jl_atomic_load_relaxed(&ptls->current_task)); + lastt->ctx.ctx = NULL; + sanitizer_finish_switch_fiber(&ptls->previous_task->ctx, &lastt->ctx); } JL_DLLEXPORT void jl_switch(void) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER @@ -629,7 +685,7 @@ JL_DLLEXPORT void jl_switch(void) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER return; } int8_t gc_state = jl_gc_unsafe_enter(ptls); - if (t->started && t->stkbuf == NULL) + if (t->ctx.started && t->ctx.stkbuf == NULL) jl_error("attempt to switch to exited task"); if (ptls->in_finalizer) jl_error("task switch not allowed from inside gc finalizer"); @@ -654,7 +710,7 @@ JL_DLLEXPORT void jl_switch(void) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER ptls->previous_task = NULL; assert(t != ct); assert(jl_atomic_load_relaxed(&t->tid) == ptls->tid); - if (!t->sticky && !t->copy_stack) + if (!t->sticky && !t->ctx.copy_stack) jl_atomic_store_release(&t->tid, -1); #else assert(ptls == ct->ptls); @@ -1071,26 +1127,28 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion jl_task_t *t = (jl_task_t*)jl_gc_alloc(ct->ptls, sizeof(jl_task_t), jl_task_type); jl_set_typetagof(t, jl_task_tag, 0); JL_PROBE_RT_NEW_TASK(ct, t); - t->copy_stack = 0; + t->ctx.copy_stack = 0; if (ssize == 0) { // stack size unspecified; use default if (always_copy_stacks) { - t->copy_stack = 1; - t->bufsz = 0; + t->ctx.copy_stack = 1; + t->ctx.bufsz = 0; } else { - t->bufsz = JL_STACK_SIZE; + t->ctx.bufsz = JL_STACK_SIZE; } - t->stkbuf = NULL; + t->ctx.stkbuf = NULL; } else { // user requested dedicated stack of a certain size if (ssize < MINSTKSZ) ssize = MINSTKSZ; - t->bufsz = ssize; - t->stkbuf = jl_alloc_fiber(&t->ctx.ctx, &t->bufsz, t); - if (t->stkbuf == NULL) + t->ctx.bufsz = ssize; + t->ctx.stkbuf = jl_malloc_stack(&t->ctx.bufsz, t); + if (t->ctx.stkbuf == NULL) { + t->ctx.bufsz = 0; jl_throw(jl_memory_exception); + } } t->next = jl_nothing; t->queue = jl_nothing; @@ -1109,30 +1167,21 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->sticky = 1; t->gcstack = NULL; t->excstack = NULL; - t->started = 0; + t->ctx.started = 0; t->priority = 0; - jl_atomic_store_relaxed(&t->tid, t->copy_stack ? jl_atomic_load_relaxed(&ct->tid) : -1); // copy_stacks are always pinned since they can't be moved + jl_atomic_store_relaxed(&t->tid, -1); t->threadpoolid = ct->threadpoolid; t->ptls = NULL; t->world_age = ct->world_age; t->reentrant_timing = 0; jl_timing_task_init(t); -#ifdef COPY_STACKS - if (!t->copy_stack) { -#if defined(JL_DEBUG_BUILD) - memset(&t->ctx, 0, sizeof(t->ctx)); -#endif - } - else { - if (always_copy_stacks) - memcpy(&t->ctx.copy_ctx, &ct->ptls->copy_stack_ctx, sizeof(t->ctx.copy_ctx)); - else - memcpy(&t->ctx.ctx, &ct->ptls->base_ctx, sizeof(t->ctx.ctx)); - } -#endif + if (t->ctx.copy_stack) + t->ctx.copy_ctx = NULL; + else + t->ctx.ctx = NULL; #ifdef _COMPILER_TSAN_ENABLED_ - t->ctx.tsan_state = __tsan_create_fiber(0); + t->ctx.tsan_state = NULL; #endif #ifdef _COMPILER_ASAN_ENABLED_ t->ctx.asan_fake_stack = NULL; @@ -1196,7 +1245,7 @@ CFI_NORETURN jl_task_t *ct = jl_current_task; #endif jl_ptls_t ptls = ct->ptls; - sanitizer_finish_switch_fiber(ptls->previous_task, ct); + sanitizer_finish_switch_fiber(&ptls->previous_task->ctx, &ct->ctx); _start_task(); } @@ -1210,6 +1259,7 @@ CFI_NORETURN #else jl_task_t *ct = jl_current_task; #endif + ct->ctx.ctx = NULL; jl_ptls_t ptls = ct->ptls; jl_value_t *res; assert(ptls->finalizers_inhibited == 0); @@ -1217,11 +1267,11 @@ CFI_NORETURN #ifdef MIGRATE_TASKS jl_task_t *pt = ptls->previous_task; ptls->previous_task = NULL; - if (!pt->sticky && !pt->copy_stack) + if (!pt->sticky && !pt->ctx.copy_stack) jl_atomic_store_release(&pt->tid, -1); #endif - ct->started = 1; + ct->ctx.started = 1; JL_PROBE_RT_START_TASK(ct); jl_timing_block_task_enter(ct, ptls, NULL); if (jl_atomic_load_relaxed(&ct->_isexception)) { @@ -1258,64 +1308,52 @@ skip_pop_exception:; #ifdef _OS_WINDOWS_ #define setcontext jl_setcontext #define swapcontext jl_swapcontext -#define makecontext jl_makecontext #endif -static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) JL_NOTSAFEPOINT +static int make_fiber(jl_ucontext_t *t, _jl_ucontext_t *ctx) { #ifndef _OS_WINDOWS_ - int r = getcontext(t); - if (r != 0) - jl_error("getcontext failed"); + int r = getcontext(ctx); + if (r != 0) abort(); #endif - void *stk = jl_malloc_stack(ssize, owner); - if (stk == NULL) - return NULL; - t->uc_stack.ss_sp = stk; - t->uc_stack.ss_size = *ssize; + ctx->uc_stack.ss_sp = (char*)t->stkbuf; + ctx->uc_stack.ss_size = t->bufsz; #ifdef _OS_WINDOWS_ - makecontext(t, &start_task); + jl_makecontext(ctx, &start_task); #else - t->uc_link = NULL; - makecontext(t, &start_task, 0); + ctx->uc_link = NULL; + makecontext(ctx, &start_task, 0); #endif - return (char*)stk; + return 1; } static void jl_start_fiber_set(jl_ucontext_t *t) { - setcontext(&t->ctx); + _jl_ucontext_t ctx; + make_fiber(t, &ctx); + setcontext(&ctx); } static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) { + _jl_ucontext_t ctx; + make_fiber(t, &ctx); assert(lastt); tsan_switch_to_ctx(t); - swapcontext(&lastt->ctx, &t->ctx); + swapcontext(lastt->ctx, &ctx); } static void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t) { tsan_switch_to_ctx(t); - swapcontext(&lastt->ctx, &t->ctx); + swapcontext(lastt->ctx, t->ctx); } static void jl_set_fiber(jl_ucontext_t *t) { - setcontext(&t->ctx); -} -#endif - -#if defined(JL_HAVE_UNW_CONTEXT) || defined(JL_HAVE_ASM) -static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) -{ - char *stkbuf = (char*)jl_malloc_stack(ssize, owner); - if (stkbuf == NULL) - return NULL; -#ifndef __clang_gcanalyzer__ - ((char**)t)[0] = stkbuf; // stash the stack pointer somewhere for start_fiber - ((size_t*)t)[1] = *ssize; // stash the stack size somewhere for start_fiber -#endif - return stkbuf; + setcontext(t->ctx); } #endif #if defined(JL_HAVE_UNW_CONTEXT) +#ifdef _OS_WINDOWS_ +#error unw_context_t not defined in Windows +#endif static inline void jl_unw_swapcontext(unw_context_t *old, unw_cursor_t *c) { volatile int returns = 0; @@ -1329,15 +1367,15 @@ static inline void jl_unw_swapcontext(unw_context_t *old, unw_cursor_t *c) static void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t) { unw_cursor_t c; - int r = unw_init_local(&c, &t->ctx); + int r = unw_init_local(&c, t->ctx); if (r < 0) abort(); - jl_unw_swapcontext(&lastt->ctx, &c); + jl_unw_swapcontext(lastt->ctx, &c); } static void jl_set_fiber(jl_ucontext_t *t) { unw_cursor_t c; - int r = unw_init_local(&c, &t->ctx); + int r = unw_init_local(&c, t->ctx); if (r < 0) abort(); unw_resume(&c); @@ -1345,14 +1383,14 @@ static void jl_set_fiber(jl_ucontext_t *t) #elif defined(JL_HAVE_ASM) static void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t) { - if (jl_setjmp(lastt->ctx.uc_mcontext, 0)) + if (jl_setjmp(lastt->ctx->uc_mcontext, 0)) return; tsan_switch_to_ctx(t); jl_set_fiber(t); // doesn't return } static void jl_set_fiber(jl_ucontext_t *t) { - jl_longjmp(t->ctx.uc_mcontext, 1); + jl_longjmp(t->ctx->uc_mcontext, 1); } #endif @@ -1373,14 +1411,14 @@ static void jl_set_fiber(jl_ucontext_t *t) static void jl_start_fiber_set(jl_ucontext_t *t) { unw_cursor_t c; - char *stk = ((char**)&t->ctx)[0]; - size_t ssize = ((size_t*)&t->ctx)[1]; + char *stk = (char*)t->stkbuf; + size_t ssize = t->bufsz; uintptr_t fn = (uintptr_t)&start_task; stk += ssize; - int r = unw_getcontext(&t->ctx); + int r = unw_getcontext(t->ctx); if (r) abort(); - if (unw_init_local(&c, &t->ctx)) + if (unw_init_local(&c, t->ctx)) abort(); PUSH_RET(&c, stk); #if defined __linux__ @@ -1396,43 +1434,46 @@ static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) { assert(lastt); unw_cursor_t c; - char *stk = ((char**)&t->ctx)[0]; - size_t ssize = ((size_t*)&t->ctx)[1]; + char *stk = (char*)t->stkbuf; + size_t ssize = t->bufsz; uintptr_t fn = (uintptr_t)&start_task; stk += ssize; volatile int returns = 0; - int r = unw_getcontext(&lastt->ctx); + int r = unw_getcontext(lastt->ctx); if (++returns == 2) // r is garbage after the first return return; if (r != 0 || returns != 1) abort(); - r = unw_getcontext(&t->ctx); + r = unw_getcontext(t->ctx); if (r != 0) abort(); - if (unw_init_local(&c, &t->ctx)) + if (unw_init_local(&c, t->ctx)) abort(); PUSH_RET(&c, stk); if (unw_set_reg(&c, UNW_REG_SP, (uintptr_t)stk)) abort(); if (unw_set_reg(&c, UNW_REG_IP, fn)) abort(); - jl_unw_swapcontext(&lastt->ctx, &c); + jl_unw_swapcontext(lastt->ctx, &c); } #endif #if defined(JL_HAVE_ASM) +#ifdef _OS_WINDOWS_ +#error JL_HAVE_ASM not defined in Windows +#endif JL_NO_ASAN static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) { assert(lastt); #ifdef JL_HAVE_UNW_CONTEXT volatile int returns = 0; - int r = unw_getcontext(&lastt->ctx); + int r = unw_getcontext(lastt->ctx); if (++returns == 2) // r is garbage after the first return return; if (r != 0 || returns != 1) abort(); #else - if (jl_setjmp(lastt->ctx.uc_mcontext, 0)) + if (jl_setjmp(lastt->ctx->uc_mcontext, 0)) return; #endif tsan_switch_to_ctx(t); @@ -1440,8 +1481,9 @@ JL_NO_ASAN static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t * } JL_NO_ASAN static void jl_start_fiber_set(jl_ucontext_t *t) { - char *stk = ((char**)&t->ctx)[0]; - size_t ssize = ((size_t*)&t->ctx)[1]; +CFI_NORETURN + char *stk = (char*)t->stkbuf; + size_t ssize = t->bufsz; uintptr_t fn = (uintptr_t)&start_task; stk += ssize; #ifdef _CPU_X86_64_ @@ -1539,14 +1581,14 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) } #endif if (always_copy_stacks) { - ct->copy_stack = 1; - ct->stkbuf = NULL; - ct->bufsz = 0; + ct->ctx.copy_stack = 1; + ct->ctx.stkbuf = NULL; + ct->ctx.bufsz = 0; } else { - ct->copy_stack = 0; - ct->stkbuf = stack; - ct->bufsz = ssize; + ct->ctx.copy_stack = 0; + ct->ctx.stkbuf = stack; + ct->ctx.bufsz = ssize; } #ifdef USE_TRACY @@ -1554,7 +1596,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) strcpy(unique_string, "Root"); ct->name = unique_string; #endif - ct->started = 1; + ct->ctx.started = 1; ct->next = jl_nothing; ct->queue = jl_nothing; ct->tls = jl_nothing; @@ -1594,21 +1636,18 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) if (always_copy_stacks) { // when this is set, we will attempt to corrupt the process stack to switch tasks, // although this is unreliable, and thus not recommended - ptls->stackbase = stack_hi; - ptls->stacksize = ssize; -#ifdef _OS_WINDOWS_ - ptls->copy_stack_ctx.uc_stack.ss_sp = stack_hi; - ptls->copy_stack_ctx.uc_stack.ss_size = ssize; -#endif - if (jl_setjmp(ptls->copy_stack_ctx.uc_mcontext, 0)) - start_task(); // sanitizer_finish_switch_fiber is part of start_task + ptls->stackbase = jl_get_frame_addr(); + ptls->stacksize = (char*)ptls->stackbase - (char*)stack_lo; } else { - ssize = JL_STACK_SIZE; - char *stkbuf = jl_alloc_fiber(&ptls->base_ctx, &ssize, NULL); + size_t bufsz = JL_STACK_SIZE; + void *stkbuf = jl_malloc_stack(&bufsz, NULL); if (stkbuf != NULL) { - ptls->stackbase = stkbuf + ssize; - ptls->stacksize = ssize; + ptls->stackbase = (char*)stkbuf + bufsz; + ptls->stacksize = bufsz; + } + else { + ptls->stacksize = 0; } } #endif @@ -1621,7 +1660,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) JL_DLLEXPORT int jl_is_task_started(jl_task_t *t) JL_NOTSAFEPOINT { - return t->started; + return t->ctx.started; } JL_DLLEXPORT int16_t jl_get_task_tid(jl_task_t *t) JL_NOTSAFEPOINT From 7d341eac799ffaa7dfbb62eaf670ab24de0e1a81 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Tue, 20 Aug 2024 17:20:16 -0500 Subject: [PATCH 16/94] Update display style of `n` in `invmod(n)` dosctring. (#55539) From https://github.com/JuliaLang/julia/pull/55531#discussion_r1722585738 --- base/intfuncs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index e44ea8eed2463..8d46fcffa3ad5 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -271,7 +271,7 @@ n * invmod(n) == 1 (n % T) * invmod(n, T) == 1 ``` Note that `*` here is modular multiplication in the integer ring, `T`. This will -throw an error if ``n`` is even, because then it is not relatively prime with `2^N` +throw an error if `n` is even, because then it is not relatively prime with `2^N` and thus has no such inverse. Specifying the modulus implied by an integer type as an explicit value is often From 86cba99f6f79bfc6b67e52f0575de211109b638c Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Wed, 21 Aug 2024 07:16:39 +0530 Subject: [PATCH 17/94] Remove specialized vector-matrix multiplication methods (#55538) The specialized methods for `TransposeAbsMat` and `AdjointAbsMat` seem unnecessary, as these are also `AbstractMatrix`es, and are treated identically. I've also added a `require_one_based_indexing` check on the vector to avoid accepting `OffsetArray`s. --- stdlib/LinearAlgebra/src/matmul.jl | 7 ++++--- stdlib/LinearAlgebra/test/matmul.jl | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index 412d375d3e842..9a232d3ad1e51 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -61,9 +61,10 @@ function (*)(A::AbstractMatrix{T}, x::AbstractVector{S}) where {T,S} end # these will throw a DimensionMismatch unless B has 1 row (or 1 col for transposed case): -(*)(a::AbstractVector, tB::TransposeAbsMat) = reshape(a, length(a), 1) * tB -(*)(a::AbstractVector, adjB::AdjointAbsMat) = reshape(a, length(a), 1) * adjB -(*)(a::AbstractVector, B::AbstractMatrix) = reshape(a, length(a), 1) * B +function (*)(a::AbstractVector, B::AbstractMatrix) + require_one_based_indexing(a) + reshape(a, length(a), 1) * B +end # Add a level of indirection and specialize _mul! to avoid ambiguities in mul! @inline mul!(y::AbstractVector, A::AbstractVecOrMat, x::AbstractVector, diff --git a/stdlib/LinearAlgebra/test/matmul.jl b/stdlib/LinearAlgebra/test/matmul.jl index 56834a39a3ceb..4c79451ebfc8b 100644 --- a/stdlib/LinearAlgebra/test/matmul.jl +++ b/stdlib/LinearAlgebra/test/matmul.jl @@ -1120,4 +1120,14 @@ end end end +@testset "vector-matrix multiplication" begin + a = [1,2] + A = reshape([1,2], 2, 1) + B = [1 2] + @test a * B ≈ A * B + B = reshape([1,2], 2, 1) + @test a * B' ≈ A * B' + @test a * transpose(B) ≈ A * transpose(B) +end + end # module TestMatmul From 54142b7a8255d2231af8182eae52417eb6c72327 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:13:39 +0900 Subject: [PATCH 18/94] effects: minor fixes for the effects system correctness (#55536) This commit implements several fixes related to the correctness of the effect system. The most significant change addresses an issue where post-opt analysis was not correctly propagating the taints of `:noub` and `:nortcall` of `:foreigncall` expressions, which could lead to incorrect effect bits. Additionally, adjustments have been made to the values of effects used in various worst-case scenarios. --- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/effects.jl | 10 +++++----- base/compiler/optimize.jl | 10 ++++++++-- base/compiler/ssair/ir.jl | 3 ++- base/compiler/ssair/irinterp.jl | 3 ++- base/compiler/tfuncs.jl | 7 ++++++- base/compiler/validation.jl | 4 +++- base/strings/string.jl | 6 ++++-- test/compiler/effects.jl | 5 +++++ test/strings/basic.jl | 2 ++ 10 files changed, 38 insertions(+), 14 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 83abfc952bf8e..c947832e97d16 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2829,7 +2829,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif ehead === :globaldecl return RTEffects(Nothing, Any, EFFECTS_UNKNOWN) elseif ehead === :thunk - return RTEffects(Any, Any, EFFECTS_UNKNOWN) + return RTEffects(Any, Any, Effects()) end # N.B.: abstract_eval_value_expr can modify the global effects, but # we move out any arguments with effects during SSA construction later diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 166df78f3130c..7778c96e019e5 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -169,12 +169,12 @@ const NOUB_IF_NOINBOUNDS = 0x01 << 1 # :nonoverlayed bits const CONSISTENT_OVERLAY = 0x01 << 1 -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) -const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) -const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_TRUE, false) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) -const _EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, false) # unknown really +const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) +const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_TRUE, false) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) -function Effects(effects::Effects = _EFFECTS_UNKNOWN; +function Effects(effects::Effects=Effects( + ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, false); consistent::UInt8 = effects.consistent, effect_free::UInt8 = effects.effect_free, nothrow::Bool = effects.nothrow, diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 936b604d373a0..fb712b1c71b12 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -59,6 +59,12 @@ const IR_FLAGS_NEEDS_EA = IR_FLAG_EFIIMO | IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM has_flag(curr::UInt32, flag::UInt32) = (curr & flag) == flag +function iscallstmt(@nospecialize stmt) + stmt isa Expr || return false + head = stmt.head + return head === :call || head === :invoke || head === :foreigncall +end + function flags_for_effects(effects::Effects) flags = zero(UInt32) if is_consistent(effects) @@ -380,7 +386,7 @@ function recompute_effects_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), elseif nothrow flag |= IR_FLAG_NOTHROW end - if !(isexpr(stmt, :call) || isexpr(stmt, :invoke)) + if !iscallstmt(stmt) # There is a bit of a subtle point here, which is that some non-call # statements (e.g. PiNode) can be UB:, however, we consider it # illegal to introduce such statements that actually cause UB (for any @@ -784,7 +790,7 @@ function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState) if !has_flag(flag, IR_FLAG_NORTCALL) # if a function call that might invoke `Core.Compiler.return_type` has been deleted, # there's no need to taint with `:nortcall`, allowing concrete evaluation - if isexpr(stmt, :call) || isexpr(stmt, :invoke) + if iscallstmt(stmt) sv.nortcall = false end end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index c665c5bef299e..960da88ddffc8 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -2,7 +2,8 @@ Core.PhiNode() = Core.PhiNode(Int32[], Any[]) -isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) || isa(stmt, EnterNode) || isexpr(stmt, :leave) +isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || + isa(stmt, ReturnNode) || isa(stmt, EnterNode) || isexpr(stmt, :leave) struct CFG blocks::Vector{BasicBlock} diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 3d49be33f39d5..1aeb87accbcd7 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -141,7 +141,8 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, rt = nothing if isa(stmt, Expr) head = stmt.head - if head === :call || head === :foreigncall || head === :new || head === :splatnew || head === :static_parameter || head === :isdefined || head === :boundscheck + if (head === :call || head === :foreigncall || head === :new || head === :splatnew || + head === :static_parameter || head === :isdefined || head === :boundscheck) (; rt, effects) = abstract_eval_statement_expr(interp, stmt, nothing, irsv) add_flag!(inst, flags_for_effects(effects)) elseif head === :invoke diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 89874b9a6df10..0c57c04a6ddea 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2966,7 +2966,7 @@ end function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState, max_methods::Int) length(argtypes) < 2 && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) - isvarargtype(argtypes[2]) && return CallMeta(Bool, Any, EFFECTS_UNKNOWN, NoCallInfo()) + isvarargtype(argtypes[2]) && return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) argtypes = argtypes[2:end] atype = argtypes_to_type(argtypes) matches = find_method_matches(interp, argtypes, atype; max_methods) @@ -3191,6 +3191,11 @@ function foreigncall_effects(@specialize(abstract_eval), e::Expr) elseif name === :jl_genericmemory_copy_slice return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow=false) end + # `:foreigncall` can potentially perform all sorts of operations, including calling + # overlay methods, but the `:foreigncall` itself is not dispatched, and there is no + # concern that the method calls that potentially occur within the `:foreigncall` will + # be executed using the wrong method table due to concrete evaluation, so using + # `EFFECTS_UNKNOWN` here and not tainting with `:nonoverlayed` is fine return EFFECTS_UNKNOWN end diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index a9f2f1eebe1b5..78db5ef5e4ed8 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -257,7 +257,9 @@ end function is_valid_rvalue(@nospecialize(x)) is_valid_argument(x) && return true - if isa(x, Expr) && x.head in (:new, :splatnew, :the_exception, :isdefined, :call, :invoke, :invoke_modify, :foreigncall, :cfunction, :gc_preserve_begin, :copyast, :new_opaque_closure) + if isa(x, Expr) && x.head in (:new, :splatnew, :the_exception, :isdefined, :call, + :invoke, :invoke_modify, :foreigncall, :cfunction, :gc_preserve_begin, :copyast, + :new_opaque_closure) return true end return false diff --git a/base/strings/string.jl b/base/strings/string.jl index 89e2ff288c3d7..f5abbead34bd1 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -102,9 +102,11 @@ function unsafe_string(p::Union{Ptr{UInt8},Ptr{Int8}}) ccall(:jl_cstr_to_string, Ref{String}, (Ptr{UInt8},), p) end -# This is @assume_effects :effect_free :nothrow :terminates_globally @ccall jl_alloc_string(n::Csize_t)::Ref{String}, +# This is `@assume_effects :total !:consistent @ccall jl_alloc_string(n::Csize_t)::Ref{String}`, # but the macro is not available at this time in bootstrap, so we write it manually. -@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0x000e)), :(convert(Csize_t, n)))) +const _string_n_override = 0x04ee +@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, + :(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override)), :(convert(Csize_t, n)))) """ String(s::AbstractString) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index a27d52d68b9a9..11c30aad0b9a4 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -1361,3 +1361,8 @@ end |> Core.Compiler.is_nothrow @test Base.infer_effects((Vector{Any},)) do xs Core.svec(xs...) end |> Core.Compiler.is_nothrow + +# effects for unknown `:foreigncall`s +@test Base.infer_effects() do + @ccall unsafecall()::Cvoid +end == Core.Compiler.EFFECTS_UNKNOWN diff --git a/test/strings/basic.jl b/test/strings/basic.jl index d8ca4d204b6f4..511b498e5cd89 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -1235,6 +1235,8 @@ end @test !Core.Compiler.is_removable_if_unused(e) || (f, Ts) end @test_throws ArgumentError Symbol("a\0a") + + @test Base._string_n_override == Core.Compiler.encode_effects_override(Base.compute_assumed_settings((:total, :(!:consistent)))) end @testset "Ensure UTF-8 DFA can never leave invalid state" begin From 58c7186d1983de304a47fdefb9a9a16f8b4901e7 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:53:59 +0900 Subject: [PATCH 19/94] inference: propagate partially initialized mutable structs more (#55533) --- base/compiler/abstractinterpretation.jl | 16 +++++++++++++--- base/compiler/typeutils.jl | 2 +- test/compiler/inference.jl | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index c947832e97d16..26bba7b51a2dd 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1027,7 +1027,7 @@ collect_const_args(arginfo::ArgInfo, start::Int) = collect_const_args(arginfo.ar function collect_const_args(argtypes::Vector{Any}, start::Int) return Any[ let a = widenslotwrapper(argtypes[i]) isa(a, Const) ? a.val : - isconstType(a) ? (a::DataType).parameters[1] : + isconstType(a) ? a.parameters[1] : (a::DataType).instance end for i = start:length(argtypes) ] end @@ -2063,11 +2063,21 @@ function form_partially_defined_struct(@nospecialize(obj), @nospecialize(name)) fldidx === nothing && return nothing nminfld = datatype_min_ninitialized(objt) if ismutabletype(objt) - fldidx == nminfld+1 || return nothing + # A mutable struct can have non-contiguous undefined fields, but `PartialStruct` cannot + # model such a state. So here `PartialStruct` can be used to represent only the + # objects where the field following the minimum initialized fields is also defined. + if fldidx ≠ nminfld+1 + # if it is already represented as a `PartialStruct`, we can add one more + # `isdefined`-field information on top of those implied by its `fields` + if !(obj isa PartialStruct && fldidx == length(obj.fields)+1) + return nothing + end + end else fldidx > nminfld || return nothing end - return PartialStruct(objt0, Any[fieldtype(objt0, i) for i = 1:fldidx]) + return PartialStruct(objt0, Any[obj isa PartialStruct && i≤length(obj.fields) ? + obj.fields[i] : fieldtype(objt0,i) for i = 1:fldidx]) end function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}, call::CallMeta) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index a4499e003cf2c..577452a873b5e 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -18,7 +18,7 @@ function hasuniquerep(@nospecialize t) iskindtype(typeof(t)) || return true # non-types are always compared by egal in the type system isconcretetype(t) && return true # these are also interned and pointer comparable if isa(t, DataType) && t.name !== Tuple.name && !isvarargtype(t) # invariant DataTypes - return _all(hasuniquerep, t.parameters) + return all(hasuniquerep, t.parameters) end return false end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 75f33a280e245..1595594fb2068 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6033,6 +6033,22 @@ end |> Core.Compiler.is_nothrow return x.b end end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((PartiallyInitialized2,); optimize=false) do x + if isdefined(x, :b) + if isdefined(x, :c) + return x.c + end + return x.b + end + return nothing +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Bool,Int,); optimize=false) do c, b + x = c ? PartiallyInitialized1(true) : PartiallyInitialized1(true, b) + if isdefined(x, :b) + return Val(x.a), x.b + end + return nothing +end |> Core.Compiler.is_nothrow # End to end test case for the partially initialized struct with `PartialStruct` @noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) From 6d7f4a209c0dc933618e4f948e7c7f8b3f036c83 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 22 Aug 2024 23:08:37 +0530 Subject: [PATCH 20/94] Use `===` to compare with `nothing` in tests (#55563) This follows the generally recommended style, and updates instances of `a == nothing` to `a === nothing` in tests, and similarly for the `!=` comparison. --- stdlib/OpenBLAS_jll/test/runtests.jl | 2 +- test/arrayops.jl | 18 +-- test/bitarray.jl | 34 ++--- test/channels.jl | 2 +- test/char.jl | 2 +- test/compiler/codegen.jl | 2 +- test/compiler/inference.jl | 4 +- test/compiler/ssair.jl | 2 +- test/copy.jl | 2 +- test/core.jl | 2 +- test/corelogging.jl | 6 +- test/dict.jl | 6 +- test/generic_map_tests.jl | 2 +- test/intrinsics.jl | 4 +- test/iobuffer.jl | 2 +- test/iterators.jl | 4 +- test/loading.jl | 16 +-- test/ranges.jl | 14 +- test/regex.jl | 2 +- test/some.jl | 4 +- test/spawn.jl | 2 +- test/strings/search.jl | 202 +++++++++++++-------------- test/strings/types.jl | 4 +- test/testdefs.jl | 2 +- 24 files changed, 170 insertions(+), 170 deletions(-) diff --git a/stdlib/OpenBLAS_jll/test/runtests.jl b/stdlib/OpenBLAS_jll/test/runtests.jl index 1d944bab8cd67..76242b2e4080e 100644 --- a/stdlib/OpenBLAS_jll/test/runtests.jl +++ b/stdlib/OpenBLAS_jll/test/runtests.jl @@ -13,5 +13,5 @@ else end @testset "OpenBLAS_jll" begin - @test dlsym(OpenBLAS_jll.libopenblas_handle, @blasfunc(openblas_set_num_threads); throw_error=false) != nothing + @test dlsym(OpenBLAS_jll.libopenblas_handle, @blasfunc(openblas_set_num_threads); throw_error=false) !== nothing end diff --git a/test/arrayops.jl b/test/arrayops.jl index f58fdb36942a2..333b68e287c4c 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -591,32 +591,32 @@ end @test findall(!, m) == [k for (k,v) in pairs(m) if !v] @test findfirst(!iszero, a) == 2 @test findfirst(a.==0) == 1 - @test findfirst(a.==5) == nothing + @test findfirst(a.==5) === nothing @test findfirst(Dict(1=>false, 2=>true)) == 2 - @test findfirst(Dict(1=>false)) == nothing + @test findfirst(Dict(1=>false)) === nothing @test findfirst(isequal(3), [1,2,4,1,2,3,4]) == 6 @test findfirst(!isequal(1), [1,2,4,1,2,3,4]) == 2 @test findfirst(isodd, [2,4,6,3,9,2,0]) == 4 - @test findfirst(isodd, [2,4,6,2,0]) == nothing + @test findfirst(isodd, [2,4,6,2,0]) === nothing @test findnext(!iszero,a,4) == 4 @test findnext(!iszero,a,5) == 6 @test findnext(!iszero,a,1) == 2 @test findnext(isequal(1),a,4) == 6 - @test findnext(isequal(5),a,4) == nothing + @test findnext(isequal(5),a,4) === nothing @test findlast(!iszero, a) == 8 @test findlast(a.==0) == 5 - @test findlast(a.==5) == nothing - @test findlast(false) == nothing # test non-AbstractArray findlast + @test findlast(a.==5) === nothing + @test findlast(false) === nothing # test non-AbstractArray findlast @test findlast(isequal(3), [1,2,4,1,2,3,4]) == 6 @test findlast(isodd, [2,4,6,3,9,2,0]) == 5 - @test findlast(isodd, [2,4,6,2,0]) == nothing + @test findlast(isodd, [2,4,6,2,0]) === nothing @test findprev(!iszero,a,4) == 4 @test findprev(!iszero,a,5) == 4 - @test findprev(!iszero,a,1) == nothing + @test findprev(!iszero,a,1) === nothing @test findprev(isequal(1),a,4) == 2 @test findprev(isequal(1),a,8) == 6 @test findprev(isodd, [2,4,5,3,9,2,0], 7) == 5 - @test findprev(isodd, [2,4,5,3,9,2,0], 2) == nothing + @test findprev(isodd, [2,4,5,3,9,2,0], 2) === nothing @test findfirst(isequal(0x00), [0x01, 0x00]) == 2 @test findlast(isequal(0x00), [0x01, 0x00]) == 2 @test findnext(isequal(0x00), [0x00, 0x01, 0x00], 2) == 3 diff --git a/test/bitarray.jl b/test/bitarray.jl index 2cf285370441e..67d8fae0eda6d 100644 --- a/test/bitarray.jl +++ b/test/bitarray.jl @@ -1357,11 +1357,11 @@ timesofar("find") @test findprev(b1, 777) == findprevnot(b2, 777) == findprev(!, b2, 777) == 777 @test findprev(b1, 776) == findprevnot(b2, 776) == findprev(!, b2, 776) == 77 @test findprev(b1, 77) == findprevnot(b2, 77) == findprev(!, b2, 77) == 77 - @test findprev(b1, 76) == findprevnot(b2, 76) == findprev(!, b2, 76) == nothing - @test findprev(b1, -1) == findprevnot(b2, -1) == findprev(!, b2, -1) == nothing - @test findprev(identity, b1, -1) == nothing - @test findprev(Returns(false), b1, -1) == nothing - @test findprev(Returns(true), b1, -1) == nothing + @test findprev(b1, 76) == findprevnot(b2, 76) == findprev(!, b2, 76) === nothing + @test findprev(b1, -1) == findprevnot(b2, -1) == findprev(!, b2, -1) === nothing + @test findprev(identity, b1, -1) === nothing + @test findprev(Returns(false), b1, -1) === nothing + @test findprev(Returns(true), b1, -1) === nothing @test_throws BoundsError findnext(b1, -1) @test_throws BoundsError findnextnot(b2, -1) @test_throws BoundsError findnext(!, b2, -1) @@ -1372,28 +1372,28 @@ timesofar("find") @test findnext(b1, 77) == findnextnot(b2, 77) == findnext(!, b2, 77) == 77 @test findnext(b1, 78) == findnextnot(b2, 78) == findnext(!, b2, 78) == 777 @test findnext(b1, 777) == findnextnot(b2, 777) == findnext(!, b2, 777) == 777 - @test findnext(b1, 778) == findnextnot(b2, 778) == findnext(!, b2, 778) == nothing - @test findnext(b1, 1001) == findnextnot(b2, 1001) == findnext(!, b2, 1001) == nothing - @test findnext(identity, b1, 1001) == findnext(Returns(false), b1, 1001) == findnext(Returns(true), b1, 1001) == nothing + @test findnext(b1, 778) == findnextnot(b2, 778) == findnext(!, b2, 778) === nothing + @test findnext(b1, 1001) == findnextnot(b2, 1001) == findnext(!, b2, 1001) === nothing + @test findnext(identity, b1, 1001) == findnext(Returns(false), b1, 1001) == findnext(Returns(true), b1, 1001) === nothing @test findlast(b1) == Base.findlastnot(b2) == 777 @test findfirst(b1) == Base.findfirstnot(b2) == 77 b0 = BitVector() - @test findprev(Returns(true), b0, -1) == nothing + @test findprev(Returns(true), b0, -1) === nothing @test_throws BoundsError findprev(Returns(true), b0, 1) @test_throws BoundsError findnext(Returns(true), b0, -1) - @test findnext(Returns(true), b0, 1) == nothing + @test findnext(Returns(true), b0, 1) === nothing b1 = falses(10) @test findprev(Returns(true), b1, 5) == 5 @test findnext(Returns(true), b1, 5) == 5 - @test findprev(Returns(true), b1, -1) == nothing - @test findnext(Returns(true), b1, 11) == nothing - @test findprev(Returns(false), b1, 5) == nothing - @test findnext(Returns(false), b1, 5) == nothing - @test findprev(Returns(false), b1, -1) == nothing - @test findnext(Returns(false), b1, 11) == nothing + @test findprev(Returns(true), b1, -1) === nothing + @test findnext(Returns(true), b1, 11) === nothing + @test findprev(Returns(false), b1, 5) === nothing + @test findnext(Returns(false), b1, 5) === nothing + @test findprev(Returns(false), b1, -1) === nothing + @test findnext(Returns(false), b1, 11) === nothing @test_throws BoundsError findprev(Returns(true), b1, 11) @test_throws BoundsError findnext(Returns(true), b1, -1) @@ -1415,7 +1415,7 @@ timesofar("find") for l = [1, 63, 64, 65, 127, 128, 129] f = falses(l) t = trues(l) - @test findprev(f, l) == findprevnot(t, l) == nothing + @test findprev(f, l) == findprevnot(t, l) === nothing @test findprev(t, l) == findprevnot(f, l) == l b1 = falses(l) b1[end] = true diff --git a/test/channels.jl b/test/channels.jl index d3415a96afd12..d62c0b581775c 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -500,7 +500,7 @@ end c = Channel(1) close(c) @test !isopen(c) - c.excp == nothing # to trigger the branch + c.excp === nothing # to trigger the branch @test_throws InvalidStateException Base.check_channel_state(c) end diff --git a/test/char.jl b/test/char.jl index 5da92121b1630..3100add0e81c5 100644 --- a/test/char.jl +++ b/test/char.jl @@ -121,7 +121,7 @@ end #iterate(c::Char) for x in testarrays @test iterate(x)[1] == x - @test iterate(x, iterate(x)[2]) == nothing + @test iterate(x, iterate(x)[2]) === nothing end #isless(x::Char, y::Integer) = isless(UInt32(x), y) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index a8128be270f12..76e5bd9e8780f 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -440,7 +440,7 @@ function f1_30093(r) end end -@test f1_30093(Ref(0)) == nothing +@test f1_30093(Ref(0)) === nothing # issue 33590 function f33590(b, x) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 1595594fb2068..6e2fa77eb15c8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1065,7 +1065,7 @@ gl_17003 = [1, 2, 3] f2_17003(item::AVector_17003) = nothing f2_17003(::Any) = f2_17003(NArray_17003(gl_17003)) -@test f2_17003(1) == nothing +@test f2_17003(1) === nothing # issue #20847 function segfaultfunction_20847(A::Vector{NTuple{N, T}}) where {N, T} @@ -1076,7 +1076,7 @@ end tuplevec_20847 = Tuple{Float64, Float64}[(0.0,0.0), (1.0,0.0)] for A in (1,) - @test segfaultfunction_20847(tuplevec_20847) == nothing + @test segfaultfunction_20847(tuplevec_20847) === nothing end # Issue #20902, check that this doesn't error. diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index 06258f52cb69c..b7d75d0be5567 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -219,7 +219,7 @@ let code = Any[ ] ir = make_ircode(code; verify=false) ir = Core.Compiler.compact!(ir, true) - @test Core.Compiler.verify_ir(ir) == nothing + @test Core.Compiler.verify_ir(ir) === nothing end # issue #37919 diff --git a/test/copy.jl b/test/copy.jl index b6ad53600027a..d2f555604c4d8 100644 --- a/test/copy.jl +++ b/test/copy.jl @@ -198,7 +198,7 @@ end bar = Bar19921(foo, Dict(foo => 3)) bar2 = deepcopy(bar) @test bar2.foo ∈ keys(bar2.fooDict) - @test bar2.fooDict[bar2.foo] != nothing + @test bar2.fooDict[bar2.foo] !== nothing end let d = IdDict(rand(2) => rand(2) for i = 1:100) diff --git a/test/core.jl b/test/core.jl index 692d91b6e05b3..648dd68602fa5 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7031,7 +7031,7 @@ translate27368(::Type{Val{name}}) where {name} = # issue #27456 @inline foo27456() = try baz_nonexistent27456(); catch; nothing; end bar27456() = foo27456() -@test bar27456() == nothing +@test bar27456() === nothing # issue #27365 mutable struct foo27365 diff --git a/test/corelogging.jl b/test/corelogging.jl index 778e70aecd406..b8cd3716cad2e 100644 --- a/test/corelogging.jl +++ b/test/corelogging.jl @@ -140,9 +140,9 @@ end end @test length(logger.logs) == 1 record = logger.logs[1] - @test record._module == nothing - @test record.file == nothing - @test record.line == nothing + @test record._module === nothing + @test record.file === nothing + @test record.line === nothing end # PR #28209 diff --git a/test/dict.jl b/test/dict.jl index e327c86521c88..13c60d5a6a053 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -8,7 +8,7 @@ using Random @test isequal(p,10=>20) @test iterate(p)[1] == 10 @test iterate(p, iterate(p)[2])[1] == 20 - @test iterate(p, iterate(p, iterate(p)[2])[2]) == nothing + @test iterate(p, iterate(p, iterate(p)[2])[2]) === nothing @test firstindex(p) == 1 @test lastindex(p) == length(p) == 2 @test Base.indexed_iterate(p, 1, nothing) == (10,2) @@ -683,9 +683,9 @@ end @inferred setindex!(d, -1, 10) @test d[10] == -1 @test 1 == @inferred d[1] - @test get(d, -111, nothing) == nothing + @test get(d, -111, nothing) === nothing @test 1 == @inferred get(d, 1, 1) - @test pop!(d, -111, nothing) == nothing + @test pop!(d, -111, nothing) === nothing @test 1 == @inferred pop!(d, 1) # get! and delete! diff --git a/test/generic_map_tests.jl b/test/generic_map_tests.jl index b155370dd6465..7f19d60fe31fb 100644 --- a/test/generic_map_tests.jl +++ b/test/generic_map_tests.jl @@ -43,7 +43,7 @@ function generic_map_tests(mapf, inplace_mapf=nothing) @test mapf(f, Int[], Int[], Complex{Int}[]) == Union{}[] # In-place map - if inplace_mapf != nothing + if inplace_mapf !== nothing A = Float64[1:10...] inplace_mapf(x -> x*x, A, A) @test A == map(x -> x*x, Float64[1:10...]) diff --git a/test/intrinsics.jl b/test/intrinsics.jl index e61354fe4f7f3..7a63cd1c0a62e 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -197,8 +197,8 @@ for order in (:not_atomic, :monotonic, :acquire, :release, :acquire_release, :se @test (order -> Core.Intrinsics.atomic_fence(order))(order) === nothing @test Base.invokelatest(@eval () -> Core.Intrinsics.atomic_fence($(QuoteNode(order)))) === nothing end -@test Core.Intrinsics.atomic_pointerref(C_NULL, :sequentially_consistent) == nothing -@test (@force_compile; Core.Intrinsics.atomic_pointerref(C_NULL, :sequentially_consistent)) == nothing +@test Core.Intrinsics.atomic_pointerref(C_NULL, :sequentially_consistent) === nothing +@test (@force_compile; Core.Intrinsics.atomic_pointerref(C_NULL, :sequentially_consistent)) === nothing primitive type Int256 <: Signed 256 end Int256(i::Int) = Core.Intrinsics.sext_int(Int256, i) diff --git a/test/iobuffer.jl b/test/iobuffer.jl index 0e74595d29d20..b5b34a2dbed8c 100644 --- a/test/iobuffer.jl +++ b/test/iobuffer.jl @@ -351,7 +351,7 @@ end a = Base.GenericIOBuffer(UInt8[], true, true, false, true, typemax(Int)) mark(a) # mark at position 0 write(a, "Hello!") - @test Base.compact(a) == nothing # because pointer > mark + @test Base.compact(a) === nothing # because pointer > mark close(a) b = Base.GenericIOBuffer(UInt8[], true, true, false, true, typemax(Int)) write(b, "Hello!") diff --git a/test/iterators.jl b/test/iterators.jl index 95275195cd7c0..0df4d9afd371a 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -499,7 +499,7 @@ end @test Base.IteratorSize(product(1:2, countfrom(1))) == Base.IsInfinite() @test Base.iterate(product()) == ((), true) -@test Base.iterate(product(), 1) == nothing +@test Base.iterate(product(), 1) === nothing # intersection @test intersect(product(1:3, 4:6), product(2:4, 3:5)) == Iterators.ProductIterator((2:3, 4:5)) @@ -993,7 +993,7 @@ end end @testset "Iterators.peel" begin - @test Iterators.peel([]) == nothing + @test Iterators.peel([]) === nothing @test Iterators.peel(1:10)[1] == 1 @test Iterators.peel(1:10)[2] |> collect == 2:10 @test Iterators.peel(x^2 for x in 2:4)[1] == 4 diff --git a/test/loading.jl b/test/loading.jl index 8310cb03c410b..51e0c45d2faf1 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -167,7 +167,7 @@ end @test root.uuid == root_uuid @test this.uuid == this_uuid - @test that == nothing + @test that === nothing write(project_file, """ name = "Root" @@ -180,8 +180,8 @@ end that = Base.identify_package("That") @test root.uuid == proj_uuid - @test this == nothing - @test that == nothing + @test this === nothing + @test that === nothing finally copy!(LOAD_PATH, old_load_path) end @@ -213,8 +213,8 @@ end that = Base.identify_package("That") @test root.uuid == root_uuid - @test this == nothing - @test that == nothing + @test this === nothing + @test that === nothing @test Base.get_uuid_name(project_file, this_uuid) == "This" finally @@ -273,8 +273,8 @@ end @test joinpath(@__DIR__, normpath(path)) == locate_package(pkg) @test Base.compilecache_path(pkg, UInt64(0)) == Base.compilecache_path(pkg, UInt64(0)) end - @test identify_package("Baz") == nothing - @test identify_package("Qux") == nothing + @test identify_package("Baz") === nothing + @test identify_package("Qux") === nothing @testset "equivalent package names" begin classes = [ ["Foo"], @@ -848,7 +848,7 @@ end proj = joinpath(tmp, "Project.toml") touch(proj) touch(joinpath(tmp, "Manifest-v1.5.toml")) - @test Base.project_file_manifest_path(proj) == nothing + @test Base.project_file_manifest_path(proj) === nothing touch(joinpath(tmp, "Manifest.toml")) man = basename(Base.project_file_manifest_path(proj)) @test man == "Manifest.toml" diff --git a/test/ranges.jl b/test/ranges.jl index d789871c6d049..16b2c6bf7b77b 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -437,17 +437,17 @@ end @testset "findfirst" begin @test findfirst(==(1), Base.IdentityUnitRange(-1:1)) == 1 @test findfirst(isequal(3), Base.OneTo(10)) == 3 - @test findfirst(==(0), Base.OneTo(10)) == nothing - @test findfirst(==(11), Base.OneTo(10)) == nothing + @test findfirst(==(0), Base.OneTo(10)) === nothing + @test findfirst(==(11), Base.OneTo(10)) === nothing @test findfirst(==(4), Int16(3):Int16(7)) === Int(2) - @test findfirst(==(2), Int16(3):Int16(7)) == nothing - @test findfirst(isequal(8), 3:7) == nothing + @test findfirst(==(2), Int16(3):Int16(7)) === nothing + @test findfirst(isequal(8), 3:7) === nothing @test findfirst(isequal(7), 1:2:10) == 4 @test findfirst(==(7), 1:2:10) == 4 - @test findfirst(==(10), 1:2:10) == nothing - @test findfirst(==(11), 1:2:10) == nothing + @test findfirst(==(10), 1:2:10) === nothing + @test findfirst(==(11), 1:2:10) === nothing @test findfirst(==(-7), 1:-1:-10) == 9 - @test findfirst(==(2),1:-1:2) == nothing + @test findfirst(==(2),1:-1:2) === nothing end @testset "reverse" begin @test reverse(reverse(1:10)) == 1:10 diff --git a/test/regex.jl b/test/regex.jl index a1d0b1b0ed69a..51802125a3467 100644 --- a/test/regex.jl +++ b/test/regex.jl @@ -213,7 +213,7 @@ r = r"" * raw"a\Eb|c" @test match(r, raw"a\Eb|c").match == raw"a\Eb|c" - @test match(r, raw"c") == nothing + @test match(r, raw"c") === nothing # error for really incompatible options @test_throws ArgumentError r"a" * Regex("b", Base.DEFAULT_COMPILER_OPTS & ~Base.PCRE.UCP, Base.DEFAULT_MATCH_OPTS) diff --git a/test/some.jl b/test/some.jl index 59ccd05be96bf..89f699d8306c3 100644 --- a/test/some.jl +++ b/test/some.jl @@ -44,8 +44,8 @@ ## == and isequal nothing -@test Some(1) != nothing -@test Some(nothing) != nothing +@test Some(1) !== nothing +@test Some(nothing) !== nothing @test !isequal(Some(1), nothing) @test !isequal(Some(nothing), nothing) diff --git a/test/spawn.jl b/test/spawn.jl index 831eac493d4aa..c1802ba1f74da 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -573,7 +573,7 @@ end @test Cmd(`foo`, env=["A=true"]).env == ["A=true"] @test Cmd(`foo`, env=("A"=>true,)).env == ["A=true"] @test Cmd(`foo`, env=["A"=>true]).env == ["A=true"] -@test Cmd(`foo`, env=nothing).env == nothing +@test Cmd(`foo`, env=nothing).env === nothing # test for interpolation of Cmd let c = setenv(`x`, "A"=>true) diff --git a/test/strings/search.jl b/test/strings/search.jl index e737096b3371d..692286359868d 100644 --- a/test/strings/search.jl +++ b/test/strings/search.jl @@ -26,19 +26,19 @@ end for str in [astr, GenericString(astr)] @test_throws BoundsError findnext(isequal('z'), str, 0) @test_throws BoundsError findnext(isequal('∀'), str, 0) - @test findfirst(isequal('x'), str) == nothing - @test findfirst(isequal('\0'), str) == nothing - @test findfirst(isequal('\u80'), str) == nothing - @test findfirst(isequal('∀'), str) == nothing + @test findfirst(isequal('x'), str) === nothing + @test findfirst(isequal('\0'), str) === nothing + @test findfirst(isequal('\u80'), str) === nothing + @test findfirst(isequal('∀'), str) === nothing @test findfirst(isequal('H'), str) == 1 @test findfirst(isequal('l'), str) == 3 @test findnext(isequal('l'), str, 4) == 4 @test findnext(isequal('l'), str, 5) == 11 - @test findnext(isequal('l'), str, 12) == nothing + @test findnext(isequal('l'), str, 12) === nothing @test findfirst(isequal(','), str) == 6 - @test findnext(isequal(','), str, 7) == nothing + @test findnext(isequal(','), str, 7) === nothing @test findfirst(isequal('\n'), str) == 14 - @test findnext(isequal('\n'), str, 15) == nothing + @test findnext(isequal('\n'), str, 15) === nothing @test_throws BoundsError findnext(isequal('ε'), str, nextind(str,lastindex(str))+1) @test_throws BoundsError findnext(isequal('a'), str, nextind(str,lastindex(str))+1) end @@ -46,59 +46,59 @@ end for str in [astr, GenericString(astr)] @test_throws BoundsError findnext('z', str, 0) @test_throws BoundsError findnext('∀', str, 0) - @test findfirst('x', str) == nothing - @test findfirst('\0', str) == nothing - @test findfirst('\u80', str) == nothing - @test findfirst('∀', str) == nothing + @test findfirst('x', str) === nothing + @test findfirst('\0', str) === nothing + @test findfirst('\u80', str) === nothing + @test findfirst('∀', str) === nothing @test findfirst('H', str) == 1 @test findfirst('l', str) == 3 @test findfirst('e', str) == 2 - @test findfirst('u', str) == nothing + @test findfirst('u', str) === nothing @test findnext('l', str, 4) == 4 @test findnext('l', str, 5) == 11 - @test findnext('l', str, 12) == nothing + @test findnext('l', str, 12) === nothing @test findfirst(',', str) == 6 - @test findnext(',', str, 7) == nothing + @test findnext(',', str, 7) === nothing @test findfirst('\n', str) == 14 - @test findnext('\n', str, 15) == nothing + @test findnext('\n', str, 15) === nothing @test_throws BoundsError findnext('ε', str, nextind(str,lastindex(str))+1) @test_throws BoundsError findnext('a', str, nextind(str,lastindex(str))+1) end # ascii backward search for str in [astr] - @test findlast(isequal('x'), str) == nothing - @test findlast(isequal('\0'), str) == nothing - @test findlast(isequal('\u80'), str) == nothing - @test findlast(isequal('∀'), str) == nothing + @test findlast(isequal('x'), str) === nothing + @test findlast(isequal('\0'), str) === nothing + @test findlast(isequal('\u80'), str) === nothing + @test findlast(isequal('∀'), str) === nothing @test findlast(isequal('H'), str) == 1 - @test findprev(isequal('H'), str, 0) == nothing + @test findprev(isequal('H'), str, 0) === nothing @test findlast(isequal('l'), str) == 11 @test findprev(isequal('l'), str, 5) == 4 @test findprev(isequal('l'), str, 4) == 4 @test findprev(isequal('l'), str, 3) == 3 - @test findprev(isequal('l'), str, 2) == nothing + @test findprev(isequal('l'), str, 2) === nothing @test findlast(isequal(','), str) == 6 - @test findprev(isequal(','), str, 5) == nothing + @test findprev(isequal(','), str, 5) === nothing @test findlast(isequal('\n'), str) == 14 end for str in [astr] - @test findlast('x', str) == nothing - @test findlast('\0', str) == nothing - @test findlast('\u80', str) == nothing - @test findlast('∀', str) == nothing + @test findlast('x', str) === nothing + @test findlast('\0', str) === nothing + @test findlast('\u80', str) === nothing + @test findlast('∀', str) === nothing @test findlast('H', str) == 1 - @test findprev('H', str, 0) == nothing + @test findprev('H', str, 0) === nothing @test findlast('l', str) == 11 @test findprev('l', str, 5) == 4 @test findprev('l', str, 4) == 4 @test findprev('l', str, 3) == 3 - @test findprev('l', str, 2) == nothing + @test findprev('l', str, 2) === nothing @test findlast(',', str) == 6 - @test findprev(',', str, 5) == nothing - @test findlast(str, "") == nothing - @test findlast(str^2, str) == nothing + @test findprev(',', str, 5) === nothing + @test findlast(str, "") === nothing + @test findlast(str^2, str) === nothing @test findlast('\n', str) == 14 end @@ -106,133 +106,133 @@ end for str in (u8str, GenericString(u8str)) @test_throws BoundsError findnext(isequal('z'), str, 0) @test_throws BoundsError findnext(isequal('∀'), str, 0) - @test findfirst(isequal('z'), str) == nothing - @test findfirst(isequal('\0'), str) == nothing - @test findfirst(isequal('\u80'), str) == nothing - @test findfirst(isequal('∄'), str) == nothing + @test findfirst(isequal('z'), str) === nothing + @test findfirst(isequal('\0'), str) === nothing + @test findfirst(isequal('\u80'), str) === nothing + @test findfirst(isequal('∄'), str) === nothing @test findfirst(isequal('∀'), str) == 1 @test_throws StringIndexError findnext(isequal('∀'), str, 2) - @test findnext(isequal('∀'), str, 4) == nothing + @test findnext(isequal('∀'), str, 4) === nothing @test findfirst(isequal('∃'), str) == 13 @test_throws StringIndexError findnext(isequal('∃'), str, 15) - @test findnext(isequal('∃'), str, 16) == nothing + @test findnext(isequal('∃'), str, 16) === nothing @test findfirst(isequal('x'), str) == 26 @test findnext(isequal('x'), str, 27) == 43 - @test findnext(isequal('x'), str, 44) == nothing + @test findnext(isequal('x'), str, 44) === nothing @test findfirst(isequal('δ'), str) == 17 @test_throws StringIndexError findnext(isequal('δ'), str, 18) @test findnext(isequal('δ'), str, nextind(str,17)) == 33 - @test findnext(isequal('δ'), str, nextind(str,33)) == nothing + @test findnext(isequal('δ'), str, nextind(str,33)) === nothing @test findfirst(isequal('ε'), str) == 5 @test findnext(isequal('ε'), str, nextind(str,5)) == 54 - @test findnext(isequal('ε'), str, nextind(str,54)) == nothing - @test findnext(isequal('ε'), str, nextind(str,lastindex(str))) == nothing - @test findnext(isequal('a'), str, nextind(str,lastindex(str))) == nothing + @test findnext(isequal('ε'), str, nextind(str,54)) === nothing + @test findnext(isequal('ε'), str, nextind(str,lastindex(str))) === nothing + @test findnext(isequal('a'), str, nextind(str,lastindex(str))) === nothing @test_throws BoundsError findnext(isequal('ε'), str, nextind(str,lastindex(str))+1) @test_throws BoundsError findnext(isequal('a'), str, nextind(str,lastindex(str))+1) end # utf-8 backward search for str in [u8str] - @test findlast(isequal('z'), str) == nothing - @test findlast(isequal('\0'), str) == nothing - @test findlast(isequal('\u80'), str) == nothing - @test findlast(isequal('∄'), str) == nothing + @test findlast(isequal('z'), str) === nothing + @test findlast(isequal('\0'), str) === nothing + @test findlast(isequal('\u80'), str) === nothing + @test findlast(isequal('∄'), str) === nothing @test findlast(isequal('∀'), str) == 1 - @test findprev(isequal('∀'), str, 0) == nothing + @test findprev(isequal('∀'), str, 0) === nothing @test findlast(isequal('∃'), str) == 13 @test findprev(isequal('∃'), str, 14) == 13 @test findprev(isequal('∃'), str, 13) == 13 - @test findprev(isequal('∃'), str, 12) == nothing + @test findprev(isequal('∃'), str, 12) === nothing @test findlast(isequal('x'), str) == 43 @test findprev(isequal('x'), str, 42) == 26 - @test findprev(isequal('x'), str, 25) == nothing + @test findprev(isequal('x'), str, 25) === nothing @test findlast(isequal('δ'), str) == 33 @test findprev(isequal('δ'), str, 32) == 17 - @test findprev(isequal('δ'), str, 16) == nothing + @test findprev(isequal('δ'), str, 16) === nothing @test findlast(isequal('ε'), str) == 54 @test findprev(isequal('ε'), str, 53) == 5 - @test findprev(isequal('ε'), str, 4) == nothing + @test findprev(isequal('ε'), str, 4) === nothing end # string forward search with a single-char string -@test findfirst("x", astr) == nothing +@test findfirst("x", astr) === nothing @test findfirst("H", astr) == 1:1 -@test findnext("H", astr, 2) == nothing +@test findnext("H", astr, 2) === nothing @test findfirst("l", astr) == 3:3 @test findnext("l", astr, 4) == 4:4 @test findnext("l", astr, 5) == 11:11 -@test findnext("l", astr, 12) == nothing +@test findnext("l", astr, 12) === nothing @test findfirst("\n", astr) == 14:14 -@test findnext("\n", astr, 15) == nothing +@test findnext("\n", astr, 15) === nothing -@test findfirst("z", u8str) == nothing -@test findfirst("∄", u8str) == nothing +@test findfirst("z", u8str) === nothing +@test findfirst("∄", u8str) === nothing @test findfirst("∀", u8str) == 1:1 -@test findnext("∀", u8str, 4) == nothing +@test findnext("∀", u8str, 4) === nothing @test findfirst("∃", u8str) == 13:13 -@test findnext("∃", u8str, 16) == nothing +@test findnext("∃", u8str, 16) === nothing @test findfirst("x", u8str) == 26:26 @test findnext("x", u8str, 27) == 43:43 -@test findnext("x", u8str, 44) == nothing +@test findnext("x", u8str, 44) === nothing @test findfirst("ε", u8str) == 5:5 @test findnext("ε", u8str, 7) == 54:54 -@test findnext("ε", u8str, 56) == nothing +@test findnext("ε", u8str, 56) === nothing # strifindprev backward search with a single-char string -@test findlast("x", astr) == nothing +@test findlast("x", astr) === nothing @test findlast("H", astr) == 1:1 @test findprev("H", astr, 2) == 1:1 -@test findprev("H", astr, 0) == nothing +@test findprev("H", astr, 0) === nothing @test findlast("l", astr) == 11:11 @test findprev("l", astr, 10) == 4:4 @test findprev("l", astr, 4) == 4:4 @test findprev("l", astr, 3) == 3:3 -@test findprev("l", astr, 2) == nothing +@test findprev("l", astr, 2) === nothing @test findlast("\n", astr) == 14:14 -@test findprev("\n", astr, 13) == nothing +@test findprev("\n", astr, 13) === nothing -@test findlast("z", u8str) == nothing -@test findlast("∄", u8str) == nothing +@test findlast("z", u8str) === nothing +@test findlast("∄", u8str) === nothing @test findlast("∀", u8str) == 1:1 -@test findprev("∀", u8str, 0) == nothing +@test findprev("∀", u8str, 0) === nothing #TODO: setting the limit in the middle of a wide char # makes findnext fail but findprev succeed. # Should findprev fail as well? -#@test findprev("∀", u8str, 2) == nothing # gives 1:3 +#@test findprev("∀", u8str, 2) === nothing # gives 1:3 @test findlast("∃", u8str) == 13:13 -@test findprev("∃", u8str, 12) == nothing +@test findprev("∃", u8str, 12) === nothing @test findlast("x", u8str) == 43:43 @test findprev("x", u8str, 42) == 26:26 -@test findprev("x", u8str, 25) == nothing +@test findprev("x", u8str, 25) === nothing @test findlast("ε", u8str) == 54:54 @test findprev("ε", u8str, 53) == 5:5 -@test findprev("ε", u8str, 4) == nothing +@test findprev("ε", u8str, 4) === nothing # string forward search with a single-char regex -@test findfirst(r"x", astr) == nothing +@test findfirst(r"x", astr) === nothing @test findfirst(r"H", astr) == 1:1 -@test findnext(r"H", astr, 2) == nothing +@test findnext(r"H", astr, 2) === nothing @test findfirst(r"l", astr) == 3:3 @test findnext(r"l", astr, 4) == 4:4 @test findnext(r"l", astr, 5) == 11:11 -@test findnext(r"l", astr, 12) == nothing +@test findnext(r"l", astr, 12) === nothing @test findfirst(r"\n", astr) == 14:14 -@test findnext(r"\n", astr, 15) == nothing -@test findfirst(r"z", u8str) == nothing -@test findfirst(r"∄", u8str) == nothing +@test findnext(r"\n", astr, 15) === nothing +@test findfirst(r"z", u8str) === nothing +@test findfirst(r"∄", u8str) === nothing @test findfirst(r"∀", u8str) == 1:1 -@test findnext(r"∀", u8str, 4) == nothing +@test findnext(r"∀", u8str, 4) === nothing @test findfirst(r"∀", u8str) == findfirst(r"\u2200", u8str) @test findnext(r"∀", u8str, 4) == findnext(r"\u2200", u8str, 4) @test findfirst(r"∃", u8str) == 13:13 -@test findnext(r"∃", u8str, 16) == nothing +@test findnext(r"∃", u8str, 16) === nothing @test findfirst(r"x", u8str) == 26:26 @test findnext(r"x", u8str, 27) == 43:43 -@test findnext(r"x", u8str, 44) == nothing +@test findnext(r"x", u8str, 44) === nothing @test findfirst(r"ε", u8str) == 5:5 @test findnext(r"ε", u8str, 7) == 54:54 -@test findnext(r"ε", u8str, 56) == nothing +@test findnext(r"ε", u8str, 56) === nothing for i = 1:lastindex(astr) @test findnext(r"."s, astr, i) == i:i end @@ -272,18 +272,18 @@ for i = 1:lastindex(u8str) end # string forward search with a two-char string literal -@test findfirst("xx", "foo,bar,baz") == nothing +@test findfirst("xx", "foo,bar,baz") === nothing @test findfirst("fo", "foo,bar,baz") == 1:2 -@test findnext("fo", "foo,bar,baz", 3) == nothing +@test findnext("fo", "foo,bar,baz", 3) === nothing @test findfirst("oo", "foo,bar,baz") == 2:3 -@test findnext("oo", "foo,bar,baz", 4) == nothing +@test findnext("oo", "foo,bar,baz", 4) === nothing @test findfirst("o,", "foo,bar,baz") == 3:4 -@test findnext("o,", "foo,bar,baz", 5) == nothing +@test findnext("o,", "foo,bar,baz", 5) === nothing @test findfirst(",b", "foo,bar,baz") == 4:5 @test findnext(",b", "foo,bar,baz", 6) == 8:9 -@test findnext(",b", "foo,bar,baz", 10) == nothing +@test findnext(",b", "foo,bar,baz", 10) === nothing @test findfirst("az", "foo,bar,baz") == 10:11 -@test findnext("az", "foo,bar,baz", 12) == nothing +@test findnext("az", "foo,bar,baz", 12) === nothing # issue #9365 # string forward search with a two-char UTF-8 (2 byte) string literal @@ -327,32 +327,32 @@ end @test findprev("\U1f596\U1f596", "\U1f596\U1f596", lastindex("\U1f596\U1f596\U1f596")) == 1:5 # string backward search with a two-char string literal -@test findlast("xx", "foo,bar,baz") == nothing +@test findlast("xx", "foo,bar,baz") === nothing @test findlast("fo", "foo,bar,baz") == 1:2 -@test findprev("fo", "foo,bar,baz", 1) == nothing +@test findprev("fo", "foo,bar,baz", 1) === nothing @test findlast("oo", "foo,bar,baz") == 2:3 -@test findprev("oo", "foo,bar,baz", 2) == nothing +@test findprev("oo", "foo,bar,baz", 2) === nothing @test findlast("o,", "foo,bar,baz") == 3:4 -@test findprev("o,", "foo,bar,baz", 1) == nothing +@test findprev("o,", "foo,bar,baz", 1) === nothing @test findlast(",b", "foo,bar,baz") == 8:9 @test findprev(",b", "foo,bar,baz", 6) == 4:5 -@test findprev(",b", "foo,bar,baz", 3) == nothing +@test findprev(",b", "foo,bar,baz", 3) === nothing @test findlast("az", "foo,bar,baz") == 10:11 -@test findprev("az", "foo,bar,baz", 10) == nothing +@test findprev("az", "foo,bar,baz", 10) === nothing # string search with a two-char regex -@test findfirst(r"xx", "foo,bar,baz") == nothing +@test findfirst(r"xx", "foo,bar,baz") === nothing @test findfirst(r"fo", "foo,bar,baz") == 1:2 -@test findnext(r"fo", "foo,bar,baz", 3) == nothing +@test findnext(r"fo", "foo,bar,baz", 3) === nothing @test findfirst(r"oo", "foo,bar,baz") == 2:3 -@test findnext(r"oo", "foo,bar,baz", 4) == nothing +@test findnext(r"oo", "foo,bar,baz", 4) === nothing @test findfirst(r"o,", "foo,bar,baz") == 3:4 -@test findnext(r"o,", "foo,bar,baz", 5) == nothing +@test findnext(r"o,", "foo,bar,baz", 5) === nothing @test findfirst(r",b", "foo,bar,baz") == 4:5 @test findnext(r",b", "foo,bar,baz", 6) == 8:9 -@test findnext(r",b", "foo,bar,baz", 10) == nothing +@test findnext(r",b", "foo,bar,baz", 10) === nothing @test findfirst(r"az", "foo,bar,baz") == 10:11 -@test findnext(r"az", "foo,bar,baz", 12) == nothing +@test findnext(r"az", "foo,bar,baz", 12) === nothing # occursin with a String and Char needle @test occursin("o", "foo") @@ -417,7 +417,7 @@ end A = T[0x40, 0x52, 0x00, 0x52, 0x00] for A in (A, @view(A[1:end]), codeunits(String(copyto!(Vector{UInt8}(undef,5), A)))) - @test findfirst(VT[0x30], A) === findfirst(==(VT(0x30)), A) == nothing + @test findfirst(VT[0x30], A) === findfirst(==(VT(0x30)), A) === nothing @test findfirst(VT[0x52], A) === 2:2 @test findfirst(==(VT(0x52)), A) === 2 @test findlast(VT[0x30], A) === findlast(==(VT(0x30)), A) === nothing diff --git a/test/strings/types.jl b/test/strings/types.jl index 771be253b1ec9..dbcf65b1d843b 100644 --- a/test/strings/types.jl +++ b/test/strings/types.jl @@ -118,8 +118,8 @@ end # search and SubString (issue #5679) let str = "Hello, world!" u = SubString(str, 1, 5) - @test findlast("World", u) == nothing - @test findlast(isequal('z'), u) == nothing + @test findlast("World", u) === nothing + @test findlast(isequal('z'), u) === nothing @test findlast("ll", u) == 3:4 end diff --git a/test/testdefs.jl b/test/testdefs.jl index b96c95045f2bd..eb0bf570b11fd 100644 --- a/test/testdefs.jl +++ b/test/testdefs.jl @@ -23,7 +23,7 @@ function runtests(name, path, isolate=true; seed=nothing) end res_and_time_data = @timed @testset "$name" begin # Random.seed!(nothing) will fail - seed != nothing && Random.seed!(seed) + seed !== nothing && Random.seed!(seed) original_depot_path = copy(Base.DEPOT_PATH) original_load_path = copy(Base.LOAD_PATH) From 6866b118fd6401cb51495100f2b05761c7ee7318 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Fri, 23 Aug 2024 03:21:44 +0530 Subject: [PATCH 21/94] LinearAlgebra: return destination in `setindex!` (#55544) Currently, in LinearAlgebra, `setindex!` occasionally returns the value, and at other times the destination array (or its parent). This PR consistently returns the destination in `setindex!`, which matches the behavior for `Array`s. Note that this does not change the behavior of `A[i,j] = v`, which still returns `v`. This only changes `setindex!`. --- stdlib/LinearAlgebra/src/bidiag.jl | 2 +- stdlib/LinearAlgebra/src/diagonal.jl | 2 +- stdlib/LinearAlgebra/src/symmetric.jl | 2 ++ stdlib/LinearAlgebra/src/tridiag.jl | 4 ++-- stdlib/LinearAlgebra/test/bidiag.jl | 3 +++ stdlib/LinearAlgebra/test/diagonal.jl | 2 ++ stdlib/LinearAlgebra/test/symmetric.jl | 8 ++++++++ stdlib/LinearAlgebra/test/tridiag.jl | 2 ++ 8 files changed, 21 insertions(+), 4 deletions(-) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index 5aa4314c9ae51..2b99cb1800409 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -186,7 +186,7 @@ end throw(ArgumentError(LazyString(lazy"cannot set entry ($i, $j) off the ", A.uplo == 'U' ? "upper" : "lower", " bidiagonal band to a nonzero value ", x))) end - return x + return A end Base._reverse(A::Bidiagonal, dims) = reverse!(Matrix(A); dims) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 77459f7cca520..92f399bb774ff 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -205,7 +205,7 @@ function setindex!(D::Diagonal, v, i::Int, j::Int) elseif !iszero(v) throw(ArgumentError(lazy"cannot set off-diagonal entry ($i, $j) to a nonzero value ($v)")) end - return v + return D end diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index c336785792588..ab7b5ee031260 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -261,6 +261,7 @@ Base._reverse(A::Symmetric, ::Colon) = Symmetric(reverse(A.data), A.uplo == 'U' @propagate_inbounds function setindex!(A::Symmetric, v, i::Integer, j::Integer) i == j || throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix")) setindex!(A.data, v, i, j) + return A end Base._reverse(A::Hermitian, dims) = reverse!(Matrix(A); dims) @@ -274,6 +275,7 @@ Base._reverse(A::Hermitian, ::Colon) = Hermitian(reverse(A.data), A.uplo == 'U' else setindex!(A.data, v, i, j) end + return A end Base.dataids(A::HermOrSym) = Base.dataids(parent(A)) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 0ba03634d82ad..c2806c21c00ff 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -476,7 +476,7 @@ Base._reverse!(A::SymTridiagonal, dims::Colon) = (reverse!(A.dv); reverse!(A.ev) else throw(ArgumentError(lazy"cannot set off-diagonal entry ($i, $j)")) end - return x + return A end ## Tridiagonal matrices ## @@ -731,7 +731,7 @@ end throw(ArgumentError(LazyString(lazy"cannot set entry ($i, $j) off ", lazy"the tridiagonal band to a nonzero value ($x)"))) end - return x + return A end ## structured matrix methods ## diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index e19d890237a26..d0153896106bf 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -124,6 +124,9 @@ Random.seed!(1) Bl = Bidiagonal(rand(elty, 10), zeros(elty, 9), 'L') @test_throws ArgumentError Bu[5, 4] = 1 @test_throws ArgumentError Bl[4, 5] = 1 + + # setindex should return the destination + @test setindex!(ubd, 1, 1, 1) === ubd end @testset "isstored" begin diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index 29f3a38473d4a..afb49b696d968 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -617,6 +617,8 @@ end @test_throws ArgumentError D[i, j] = 1 end end + # setindex should return the destination + @test setindex!(D, 1, 1, 1) === D end @testset "Test reverse" begin diff --git a/stdlib/LinearAlgebra/test/symmetric.jl b/stdlib/LinearAlgebra/test/symmetric.jl index 5f1293ab2cdd7..939e677039dc7 100644 --- a/stdlib/LinearAlgebra/test/symmetric.jl +++ b/stdlib/LinearAlgebra/test/symmetric.jl @@ -1127,4 +1127,12 @@ end end end +@testset "setindex! returns the destination" begin + M = rand(2,2) + for T in (Symmetric, Hermitian) + S = T(M) + @test setindex!(S, 0, 2, 2) === S + end +end + end # module TestSymmetric diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index e0a8e32d77852..1aae03c49e686 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -262,6 +262,8 @@ end @test_throws ArgumentError A[3, 2] = 1 # test assignment on the subdiagonal @test_throws ArgumentError A[2, 3] = 1 # test assignment on the superdiagonal end + # setindex! should return the destination + @test setindex!(A, A[2,2], 2, 2) === A end @testset "diag" begin @test (@inferred diag(A))::typeof(d) == d From 0c642832dc420c4bb239c846ca496f9d4e3d7927 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:31:54 +0900 Subject: [PATCH 22/94] better implementations for `unionlen`/`uniontypes` (#55561) - unify the dispatch targets - removed unnecessary `_uniontypes(::MustAlias)` method --- base/compiler/typelattice.jl | 2 -- base/reflection.jl | 14 ++++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index d375d61c0bdd8..14477c5dc2725 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -142,8 +142,6 @@ end MustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) = MustAlias(slot_id(var), vartyp, fldidx, fldtyp) -_uniontypes(x::MustAlias, ts) = _uniontypes(widenconst(x), ts) - """ alias::InterMustAlias diff --git a/base/reflection.jl b/base/reflection.jl index 6dfaf34bc0047..4b491ca9f6bd4 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1199,11 +1199,17 @@ hasgenerator(m::Core.MethodInstance) = hasgenerator(m.def::Method) # low-level method lookup functions used by the compiler -unionlen(x::Union) = unionlen(x.a) + unionlen(x.b) -unionlen(@nospecialize(x)) = 1 +unionlen(@nospecialize(x)) = x isa Union ? unionlen(x.a) + unionlen(x.b) : 1 -_uniontypes(x::Union, ts) = (_uniontypes(x.a,ts); _uniontypes(x.b,ts); ts) -_uniontypes(@nospecialize(x), ts) = (push!(ts, x); ts) +function _uniontypes(@nospecialize(x), ts::Array{Any,1}) + if x isa Union + _uniontypes(x.a, ts) + _uniontypes(x.b, ts) + else + push!(ts, x) + end + return ts +end uniontypes(@nospecialize(x)) = _uniontypes(x, Any[]) function _methods(@nospecialize(f), @nospecialize(t), lim::Int, world::UInt) From 3d20a9210a59097b46ed2cbdcd1e87435873bcfa Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Fri, 23 Aug 2024 12:55:46 +0530 Subject: [PATCH 23/94] Fix indexing in _mapreducedim for OffsetArrays (#55506) The destination array was being indexed incorrectly if it had offset indices. This led to the following on nightly: ```julia julia> using OffsetArrays julia> r = 5:100; julia> a = OffsetVector(r, 2); julia> sum(a, dims=1) 1-element OffsetArray(::Vector{Int64}, 3:3) with eltype Int64 with indices 3:3: 0 julia> sum(a) 5040 ``` The indexing was marked `@inbounds`, so this was not throwing an error. This PR also follows #55329 and only marks the indexing operations as `@inbounds`, omitting the function calls. --------- Co-authored-by: Matt Bauman --- base/reducedim.jl | 5 +++-- test/offsetarray.jl | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/base/reducedim.jl b/base/reducedim.jl index e74fe2b765277..0478afe1a46b6 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -258,8 +258,9 @@ function _mapreducedim!(f, op, R::AbstractArray, A::AbstractArrayOrBroadcasted) # use mapreduce_impl, which is probably better tuned to achieve higher performance nslices = div(length(A), lsiz) ibase = first(LinearIndices(A))-1 - for i = 1:nslices - @inbounds R[i] = op(R[i], mapreduce_impl(f, op, A, ibase+1, ibase+lsiz)) + for i in eachindex(R) + r = op(@inbounds(R[i]), mapreduce_impl(f, op, A, ibase+1, ibase+lsiz)) + @inbounds R[i] = r ibase += lsiz end return R diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 5ee918e85faf7..fb5855dfbaa0d 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -907,3 +907,10 @@ end v = view([1,2,3,4], :) @test v[Base.IdentityUnitRange(2:3)] == OffsetArray(2:3, 2:3) end + +@testset "mapreduce with OffsetRanges" begin + r = 5:100 + a = OffsetArray(r, 2) + b = sum(a, dims=1) + @test b[begin] == sum(r) +end From 5c1cfb3cf32d80235b2262ab73eb3cf5b66c8c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= <765740+giordano@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:25:07 +0200 Subject: [PATCH 24/94] [Profile] Replace `SIGTERM` with `SIGQUIT` (#55566) The `SIGQUIT` signal is handled specially in CI and automatically uploads failure information artifacts, which may be helpful to debug failures. --------- Co-authored-by: Jameson Nash --- stdlib/Profile/test/runtests.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index cbfdde61d7054..2b1ab546161c6 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -172,8 +172,10 @@ let cmd = Base.julia_cmd() t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure println("KILLING debuginfo registration test BY PROFILE TEST WATCHDOG\n") - kill(p, Base.SIGTERM) - sleep(10) + kill(p, Base.SIGQUIT) + sleep(30) + kill(p, Base.SIGQUIT) + sleep(30) kill(p, Base.SIGKILL) end s = read(p, String) @@ -202,8 +204,10 @@ if Sys.isbsd() || Sys.islinux() t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure println("KILLING siginfo/sigusr1 test BY PROFILE TEST WATCHDOG\n") - kill(p, Base.SIGTERM) - sleep(10) + kill(p, Base.SIGQUIT) + sleep(30) + kill(p, Base.SIGQUIT) + sleep(30) kill(p, Base.SIGKILL) close(notify_exit) end From 94c9d0ada7ce76b8b1c794e77fbc7d2b10ebc2b6 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sat, 24 Aug 2024 04:57:44 +0200 Subject: [PATCH 25/94] document `fourthroot` (#55560) Updates #19529 --- doc/src/base/math.md | 1 + doc/src/manual/mathematical-operations.md | 31 ++++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/doc/src/base/math.md b/doc/src/base/math.md index 7091aa6f1aa87..4f816ce2a6c1d 100644 --- a/doc/src/base/math.md +++ b/doc/src/base/math.md @@ -166,6 +166,7 @@ Base.flipsign Base.sqrt(::Number) Base.isqrt Base.Math.cbrt(::AbstractFloat) +Base.fourthroot(::Number) Base.real Base.imag Base.reim diff --git a/doc/src/manual/mathematical-operations.md b/doc/src/manual/mathematical-operations.md index 1d613931669fc..d2cef68bd6fff 100644 --- a/doc/src/manual/mathematical-operations.md +++ b/doc/src/manual/mathematical-operations.md @@ -551,21 +551,22 @@ See [Conversion and Promotion](@ref conversion-and-promotion) for how to define ### Powers, logs and roots -| Function | Description | -|:------------------------ |:-------------------------------------------------------------------------- | -| [`sqrt(x)`](@ref), `√x` | square root of `x` | -| [`cbrt(x)`](@ref), `∛x` | cube root of `x` | -| [`hypot(x, y)`](@ref) | hypotenuse of right-angled triangle with other sides of length `x` and `y` | -| [`exp(x)`](@ref) | natural exponential function at `x` | -| [`expm1(x)`](@ref) | accurate `exp(x) - 1` for `x` near zero | -| [`ldexp(x, n)`](@ref) | `x * 2^n` computed efficiently for integer values of `n` | -| [`log(x)`](@ref) | natural logarithm of `x` | -| [`log(b, x)`](@ref) | base `b` logarithm of `x` | -| [`log2(x)`](@ref) | base 2 logarithm of `x` | -| [`log10(x)`](@ref) | base 10 logarithm of `x` | -| [`log1p(x)`](@ref) | accurate `log(1 + x)` for `x` near zero | -| [`exponent(x)`](@ref) | binary exponent of `x` | -| [`significand(x)`](@ref) | binary significand (a.k.a. mantissa) of a floating-point number `x` | +| Function | Description | +|:----------------------------- |:-------------------------------------------------------------------------- | +| [`sqrt(x)`](@ref), `√x` | square root of `x` | +| [`cbrt(x)`](@ref), `∛x` | cube root of `x` | +| [`fourthroot(x)`](@ref), `∜x` | fourth root of `x` | +| [`hypot(x, y)`](@ref) | hypotenuse of right-angled triangle with other sides of length `x` and `y` | +| [`exp(x)`](@ref) | natural exponential function at `x` | +| [`expm1(x)`](@ref) | accurate `exp(x) - 1` for `x` near zero | +| [`ldexp(x, n)`](@ref) | `x * 2^n` computed efficiently for integer values of `n` | +| [`log(x)`](@ref) | natural logarithm of `x` | +| [`log(b, x)`](@ref) | base `b` logarithm of `x` | +| [`log2(x)`](@ref) | base 2 logarithm of `x` | +| [`log10(x)`](@ref) | base 10 logarithm of `x` | +| [`log1p(x)`](@ref) | accurate `log(1 + x)` for `x` near zero | +| [`exponent(x)`](@ref) | binary exponent of `x` | +| [`significand(x)`](@ref) | binary significand (a.k.a. mantissa) of a floating-point number `x` | For an overview of why functions like [`hypot`](@ref), [`expm1`](@ref), and [`log1p`](@ref) are necessary and useful, see John D. Cook's excellent pair of blog posts on the subject: [expm1, log1p, erfc](https://www.johndcook.com/blog/2010/06/07/math-library-functions-that-seem-unnecessary/), From eaa2edd6ac7b8012af0f67e3993873a0e8945882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= <765740+giordano@users.noreply.github.com> Date: Sat, 24 Aug 2024 08:47:20 +0200 Subject: [PATCH 26/94] Add tests for alignment of `Int128`/`UInt128` (#55565) --- test/compiler/codegen.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 76e5bd9e8780f..0260113044a3b 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -984,3 +984,18 @@ end @test g55208((Union{}, true, true), 0) === typeof(Union{}) @test string((Core.Union{}, true, true, true)) == "(Union{}, true, true, true)" + +# Issue #55558 +for (T, StructName) in ((Int128, :Issue55558), (UInt128, :UIssue55558)) + @eval begin + struct $(StructName) + a::$(T) + b::Int64 + c::$(T) + end + local broken_i128 = Base.BinaryPlatforms.arch(Base.BinaryPlatforms.HostPlatform()) == "powerpc64le" + @test fieldoffset($(StructName), 2) == 16 + @test fieldoffset($(StructName), 3) == 32 broken=broken_i128 + @test sizeof($(StructName)) == 48 broken=broken_i128 + end +end From 083bd8f687bb2a0608a1b0b4c99f811eecb56b3e Mon Sep 17 00:00:00 2001 From: Devansh Date: Sat, 24 Aug 2024 04:13:40 -0400 Subject: [PATCH 27/94] added cholesky of cholesky (#55559) Currently, if you have a cholesky matrix, you cannot call `cholesky` on it again: ``` using LinearAlgebra N = 10 A = randn(N, N) P = Symmetric(A * A' + I) C = cholesky(P) CC = cholesky(C) # this line throws an error ``` This small PR provides the fix. --------- Co-authored-by: Jeff Bezanson --- stdlib/LinearAlgebra/src/cholesky.jl | 3 +++ stdlib/LinearAlgebra/test/cholesky.jl | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/stdlib/LinearAlgebra/src/cholesky.jl b/stdlib/LinearAlgebra/src/cholesky.jl index cb7c6b94d4ca6..545d92ec1704d 100644 --- a/stdlib/LinearAlgebra/src/cholesky.jl +++ b/stdlib/LinearAlgebra/src/cholesky.jl @@ -551,6 +551,9 @@ end # allow packages like SparseArrays.jl to hook into here and redirect to out-of-place `cholesky` _cholesky(A::AbstractMatrix, args...; kwargs...) = cholesky!(A, args...; kwargs...) +# allow cholesky of cholesky +cholesky(A::Cholesky) = A + ## With pivoting """ cholesky(A, RowMaximum(); tol = 0.0, check = true) -> CholeskyPivoted diff --git a/stdlib/LinearAlgebra/test/cholesky.jl b/stdlib/LinearAlgebra/test/cholesky.jl index 2bcc6208c12df..00bfc18a21638 100644 --- a/stdlib/LinearAlgebra/test/cholesky.jl +++ b/stdlib/LinearAlgebra/test/cholesky.jl @@ -630,4 +630,14 @@ end end end +@testset "cholesky_of_cholesky" begin + for T in (Float64, ComplexF64), uplo in (:U, :L) + A = randn(T, 100, 100) + P = Hermitian(A' * A, uplo) + C = cholesky(P) + CC = cholesky(C) + @test C == CC + end +end + end # module TestCholesky From eb5587dac02d1f6edf486a71b95149139cc5d9f7 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 25 Aug 2024 09:05:57 +0530 Subject: [PATCH 28/94] Fix self-recursion in generic triangular (l/r)mul! (#55547) This fixes a stack-overflow in the following: ```julia julia> using LinearAlgebra julia> struct MyTriangularWithoutLRMul{T, A<:LinearAlgebra.AbstractTriangular{T}} <: LinearAlgebra.AbstractTriangular{T} data :: A end julia> Base.size(A::MyTriangularWithoutLRMul) = size(A.data) julia> Base.getindex(A::MyTriangularWithoutLRMul, i::Int, j::Int) = A.data[i,j] julia> M = MyTriangularWithoutLRMul(UpperTriangular(rand(4,4))); julia> A = rand(4,4); julia> lmul!(M, A) Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable. ERROR: StackOverflowError: Stacktrace: [1] unsafe_copyto! @ ./genericmemory.jl:122 [inlined] [2] _copyto_impl! @ ./array.jl:308 [inlined] [3] copyto! @ ./array.jl:299 [inlined] [4] copyto! @ ./array.jl:322 [inlined] [5] _trimul!(C::Matrix{Float64}, A::MyTriangularWithoutLRMul{Float64, UpperTriangular{Float64, Matrix{Float64}}}, B::Matrix{Float64}) @ LinearAlgebra ~/.julia/juliaup/julia-nightly/share/julia/stdlib/v1.12/LinearAlgebra/src/triangular.jl:961 [6] lmul!(A::MyTriangularWithoutLRMul{Float64, UpperTriangular{Float64, Matrix{Float64}}}, B::Matrix{Float64}) @ LinearAlgebra ~/.julia/juliaup/julia-nightly/share/julia/stdlib/v1.12/LinearAlgebra/src/triangular.jl:982--- the above 2 lines are repeated 39990 more times --- ``` This is done by rerouting the generic `lmul!`/`rmul!` methods to those for `UpperTriangular` or `LowerTriangular`, depending on which triangular half is populated. A similar issue with `ldiv!`/`rdiv!` is also resolved. --- stdlib/LinearAlgebra/src/triangular.jl | 33 ++++++++++++++++++++---- stdlib/LinearAlgebra/test/triangular.jl | 34 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 365ce8ee4bae2..923e13e488c85 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -979,9 +979,20 @@ _trimul!(C::AbstractMatrix, A::UpperOrLowerTriangular, B::AbstractTriangular) = _trimul!(C::AbstractMatrix, A::AbstractTriangular, B::UpperOrLowerTriangular) = generic_mattrimul!(C, uplo_char(B), isunit_char(B), wrapperop(parent(B)), A, _unwrap_at(parent(B))) -lmul!(A::AbstractTriangular, B::AbstractVecOrMat) = @inline _trimul!(B, A, B) -rmul!(A::AbstractMatrix, B::AbstractTriangular) = @inline _trimul!(A, A, B) - +function lmul!(A::AbstractTriangular, B::AbstractVecOrMat) + if istriu(A) + _trimul!(B, UpperTriangular(A), B) + else + _trimul!(B, LowerTriangular(A), B) + end +end +function rmul!(A::AbstractMatrix, B::AbstractTriangular) + if istriu(B) + _trimul!(A, A, UpperTriangular(B)) + else + _trimul!(A, A, LowerTriangular(B)) + end +end for TC in (:AbstractVector, :AbstractMatrix) @eval @inline function _mul!(C::$TC, A::AbstractTriangular, B::AbstractVector, alpha::Number, beta::Number) @@ -1017,8 +1028,20 @@ _ldiv!(C::AbstractVecOrMat, A::UpperOrLowerTriangular, B::AbstractVecOrMat) = _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::UpperOrLowerTriangular) = generic_mattridiv!(C, uplo_char(B), isunit_char(B), wrapperop(parent(B)), A, _unwrap_at(parent(B))) -ldiv!(A::AbstractTriangular, B::AbstractVecOrMat) = @inline _ldiv!(B, A, B) -rdiv!(A::AbstractMatrix, B::AbstractTriangular) = @inline _rdiv!(A, A, B) +function ldiv!(A::AbstractTriangular, B::AbstractVecOrMat) + if istriu(A) + _ldiv!(B, UpperTriangular(A), B) + else + _ldiv!(B, LowerTriangular(A), B) + end +end +function rdiv!(A::AbstractMatrix, B::AbstractTriangular) + if istriu(B) + _rdiv!(A, A, UpperTriangular(B)) + else + _rdiv!(A, A, LowerTriangular(B)) + end +end # preserve triangular structure in in-place multiplication/division for (cty, aty, bty) in ((:UpperTriangular, :UpperTriangular, :UpperTriangular), diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index 3f7cea91ec6d4..a9aeab5650f44 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -25,6 +25,12 @@ debug && println("Test basic type functionality") @test_throws DimensionMismatch LowerTriangular(randn(5, 4)) @test LowerTriangular(randn(3, 3)) |> t -> [size(t, i) for i = 1:3] == [size(Matrix(t), i) for i = 1:3] +struct MyTriangular{T, A<:LinearAlgebra.AbstractTriangular{T}} <: LinearAlgebra.AbstractTriangular{T} + data :: A +end +Base.size(A::MyTriangular) = size(A.data) +Base.getindex(A::MyTriangular, i::Int, j::Int) = A.data[i,j] + # The following test block tries to call all methods in base/linalg/triangular.jl in order for a combination of input element types. Keep the ordering when adding code. @testset for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}, Int) # Begin loop for first Triangular matrix @@ -1194,4 +1200,32 @@ end end end +@testset "(l/r)mul! and (l/r)div! for generic triangular" begin + @testset for T in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) + M = MyTriangular(T(rand(4,4))) + A = rand(4,4) + Ac = similar(A) + @testset "lmul!" begin + Ac .= A + lmul!(M, Ac) + @test Ac ≈ M * A + end + @testset "rmul!" begin + Ac .= A + rmul!(Ac, M) + @test Ac ≈ A * M + end + @testset "ldiv!" begin + Ac .= A + ldiv!(M, Ac) + @test Ac ≈ M \ A + end + @testset "rdiv!" begin + Ac .= A + rdiv!(Ac, M) + @test Ac ≈ A / M + end + end +end + end # module TestTriangular From 638a049a6c6f8f0044876942a7fad2e92020695a Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 25 Aug 2024 14:34:36 +0530 Subject: [PATCH 29/94] Quick return for empty arrays in bidiagonal matrix multiplications (#55414) --- stdlib/LinearAlgebra/src/bidiag.jl | 28 +++++++++++++++++----------- stdlib/LinearAlgebra/test/bidiag.jl | 22 ++++++++++++++++++++++ stdlib/LinearAlgebra/test/tridiag.jl | 22 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index 2b99cb1800409..a997347eabd58 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -535,12 +535,18 @@ function rmul!(B::Bidiagonal, D::Diagonal) end @noinline function check_A_mul_B!_sizes((mC, nC)::NTuple{2,Integer}, (mA, nA)::NTuple{2,Integer}, (mB, nB)::NTuple{2,Integer}) + # check for matching sizes in one column of B and C + check_A_mul_B!_sizes((mC,), (mA, nA), (mB,)) + # ensure that the number of columns in B and C match + if nB != nC + throw(DimensionMismatch(lazy"second dimension of output C, $nC, and second dimension of B, $nB, must match")) + end +end +@noinline function check_A_mul_B!_sizes((mC,)::Tuple{Integer}, (mA, nA)::NTuple{2,Integer}, (mB,)::Tuple{Integer}) if mA != mC throw(DimensionMismatch(lazy"first dimension of A, $mA, and first dimension of output C, $mC, must match")) elseif nA != mB throw(DimensionMismatch(lazy"second dimension of A, $nA, and first dimension of B, $mB, must match")) - elseif nB != nC - throw(DimensionMismatch(lazy"second dimension of output C, $nC, and second dimension of B, $nB, must match")) end end @@ -563,8 +569,10 @@ _mul!(C::AbstractMatrix, A::BiTriSym, B::TriSym, _add::MulAddMul) = _mul!(C::AbstractMatrix, A::BiTriSym, B::Bidiagonal, _add::MulAddMul) = _bibimul!(C, A, B, _add) function _bibimul!(C, A, B, _add) + require_one_based_indexing(C) check_A_mul_B!_sizes(size(C), size(A), size(B)) n = size(A,1) + iszero(n) && return C n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) # We use `_rmul_or_fill!` instead of `_modify!` here since using # `_modify!` in the following loop will not update the @@ -727,15 +735,10 @@ end function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulAddMul) require_one_based_indexing(C, B) + check_A_mul_B!_sizes(size(C), size(A), size(B)) nA = size(A,1) nB = size(B,2) - if !(size(C,1) == size(B,1) == nA) - throw(DimensionMismatch(lazy"A has first dimension $nA, B has $(size(B,1)), C has $(size(C,1)) but all must match")) - end - if size(C,2) != nB - throw(DimensionMismatch(lazy"A has second dimension $nA, B has $(size(B,2)), C has $(size(C,2)) but all must match")) - end - iszero(nA) && return C + (iszero(nA) || iszero(nB)) && return C iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) nA <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) l = _diag(A, -1) @@ -758,9 +761,10 @@ end function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::TriSym, _add::MulAddMul) require_one_based_indexing(C, A) check_A_mul_B!_sizes(size(C), size(A), size(B)) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) n = size(A,1) m = size(B,2) + (iszero(m) || iszero(n)) && return C + iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) if n <= 3 || m <= 1 return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) end @@ -793,11 +797,12 @@ end function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::Bidiagonal, _add::MulAddMul) require_one_based_indexing(C, A) check_A_mul_B!_sizes(size(C), size(A), size(B)) + m, n = size(A) + (iszero(m) || iszero(n)) && return C iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) if size(A, 1) <= 3 || size(B, 2) <= 1 return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) end - m, n = size(A) @inbounds if B.uplo == 'U' for i in 1:m for j in n:-1:2 @@ -824,6 +829,7 @@ function _dibimul!(C, A, B, _add) require_one_based_indexing(C) check_A_mul_B!_sizes(size(C), size(A), size(B)) n = size(A,1) + iszero(n) && return C n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) _rmul_or_fill!(C, _add.beta) # see the same use above iszero(_add.alpha) && return C diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index d0153896106bf..387657ba12d04 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -1023,4 +1023,26 @@ end @test_throws "cannot set entry" B[1,2] = 4 end +@testset "mul with empty arrays" begin + A = zeros(5,0) + B = Bidiagonal(zeros(0), zeros(0), :U) + BL = Bidiagonal(zeros(5), zeros(4), :U) + @test size(A * B) == size(A) + @test size(BL * A) == size(A) + @test size(B * B) == size(B) + C = similar(A) + @test mul!(C, A, B) == A * B + @test mul!(C, BL, A) == BL * A + @test mul!(similar(B), B, B) == B * B + @test mul!(similar(B, size(B)), B, B) == B * B + + v = zeros(size(B,2)) + @test size(B * v) == size(v) + @test mul!(similar(v), B, v) == B * v + + D = Diagonal(zeros(size(B,2))) + @test size(B * D) == size(D * B) == size(D) + @test mul!(similar(D), B, D) == mul!(similar(D), D, B) == B * D +end + end # module TestBidiagonal diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index 1aae03c49e686..759d692f8bc68 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -919,6 +919,28 @@ end end end +@testset "mul with empty arrays" begin + A = zeros(5,0) + T = Tridiagonal(zeros(0), zeros(0), zeros(0)) + TL = Tridiagonal(zeros(4), zeros(5), zeros(4)) + @test size(A * T) == size(A) + @test size(TL * A) == size(A) + @test size(T * T) == size(T) + C = similar(A) + @test mul!(C, A, T) == A * T + @test mul!(C, TL, A) == TL * A + @test mul!(similar(T), T, T) == T * T + @test mul!(similar(T, size(T)), T, T) == T * T + + v = zeros(size(T,2)) + @test size(T * v) == size(v) + @test mul!(similar(v), T, v) == T * v + + D = Diagonal(zeros(size(T,2))) + @test size(T * D) == size(D * T) == size(D) + @test mul!(similar(D), T, D) == mul!(similar(D), D, T) == T * D +end + @testset "show" begin T = Tridiagonal(1:3, 1:4, 1:3) @test sprint(show, T) == "Tridiagonal(1:3, 1:4, 1:3)" From adb323f1b51b65dbd17ffe1d2325a3e7d928b2ff Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 25 Aug 2024 12:41:21 +0200 Subject: [PATCH 30/94] include the `[]` doc string into the docs (#55400) --- doc/src/base/base.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 1a8cd29f91066..b5d50a846ce89 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -106,6 +106,7 @@ where . -> :: +[] ``` ## Standard Modules From 0c8641aacb53e6f4c7f21dd08e35873a2801a8f8 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Sun, 25 Aug 2024 10:21:58 -0300 Subject: [PATCH 31/94] Empty out loaded_precompiles dict instead of asserting it's empty. (#55564) This dict will contain things if we load a package image during precompilation --- base/Base.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/Base.jl b/base/Base.jl index 081426fa94d67..10a8dd1532f92 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -647,7 +647,7 @@ function __init__() init_active_project() append!(empty!(_sysimage_modules), keys(loaded_modules)) empty!(explicit_loaded_modules) - @assert isempty(loaded_precompiles) + empty!(loaded_precompiles) # If we load a packageimage when building the image this might not be empty for (mod, key) in module_keys loaded_precompiles[key => module_build_id(mod)] = mod end From 383c8efcc70c44b6e3bf07199e3f7ad5a14cde42 Mon Sep 17 00:00:00 2001 From: Kiran Pamnany Date: Sun, 25 Aug 2024 12:02:34 -0400 Subject: [PATCH 32/94] Redact object data in heap snapshots, with option to opt-out (#55326) The contents of strings can contain user data which may be proprietary and emitting them in the heap snapshot makes the heap snapshot a potential vulnerability rather than a useful debugging artifact. There are likely other tweaks necessary to make heap snapshots "safe", but this is one less. --------- Co-authored-by: Nathan Daly Co-authored-by: Ian Butterworth --- NEWS.md | 4 ++++ src/gc-heap-snapshot.cpp | 7 +++++-- src/gc-heap-snapshot.h | 2 +- stdlib/Profile/src/Profile.jl | 28 ++++++++++++++++------------ stdlib/Profile/test/runtests.jl | 21 ++++++++++++++++++--- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4bbe7645165dd..b5caaf5376fb5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -135,6 +135,10 @@ Standard library changes #### Profile +* `Profile.take_heap_snapshot` takes a new keyword argument, `redact_data::Bool`, + that is `true` by default. When set, the contents of Julia objects are not emitted + in the heap snapshot. This currently only applies to strings. ([#55326]) + #### Random #### REPL diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index b84d1f96f273c..4fcc66495fc45 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -182,6 +182,7 @@ struct HeapSnapshot { // global heap snapshot, mutated by garbage collector // when snapshotting is on. int gc_heap_snapshot_enabled = 0; +int gc_heap_snapshot_redact_data = 0; HeapSnapshot *g_snapshot = nullptr; // mutex for gc-heap-snapshot. jl_mutex_t heapsnapshot_lock; @@ -195,7 +196,7 @@ void _add_synthetic_root_entries(HeapSnapshot *snapshot) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, - ios_t *strings, ios_t *json, char all_one) + ios_t *strings, ios_t *json, char all_one, char redact_data) { HeapSnapshot snapshot; snapshot.nodes = nodes; @@ -207,6 +208,7 @@ JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, // Enable snapshotting g_snapshot = &snapshot; + gc_heap_snapshot_redact_data = redact_data; gc_heap_snapshot_enabled = true; _add_synthetic_root_entries(&snapshot); @@ -216,6 +218,7 @@ JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, // Disable snapshotting gc_heap_snapshot_enabled = false; + gc_heap_snapshot_redact_data = 0; g_snapshot = nullptr; jl_mutex_unlock(&heapsnapshot_lock); @@ -328,7 +331,7 @@ size_t record_node_to_gc_snapshot(jl_value_t *a) JL_NOTSAFEPOINT if (jl_is_string(a)) { node_type = "String"; - name = jl_string_data(a); + name = gc_heap_snapshot_redact_data ? "" : jl_string_data(a); self_size = jl_string_len(a); } else if (jl_is_symbol(a)) { diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h index a58f58aba5458..e7fbb36249ec1 100644 --- a/src/gc-heap-snapshot.h +++ b/src/gc-heap-snapshot.h @@ -122,7 +122,7 @@ static inline void gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t i // Functions to call from Julia to take heap snapshot // --------------------------------------------------------------------- JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, - ios_t *strings, ios_t *json, char all_one); + ios_t *strings, ios_t *json, char all_one, char redact_data); #ifdef __cplusplus diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 55352cc4b3a9c..799f23034b9ac 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -1250,8 +1250,10 @@ end """ - Profile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false) - Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false) + Profile.take_heap_snapshot(filepath::String, all_one::Bool=false; + redact_data::Bool=true, streaming::Bool=false) + Profile.take_heap_snapshot(all_one::Bool=false; redact_data:Bool=true, + dir::String=nothing, streaming::Bool=false) Write a snapshot of the heap, in the JSON format expected by the Chrome Devtools Heap Snapshot viewer (.heapsnapshot extension) to a file @@ -1262,6 +1264,8 @@ full file path, or IO stream. If `all_one` is true, then report the size of every object as one so they can be easily counted. Otherwise, report the actual size. +If `redact_data` is true (default), then do not emit the contents of any object. + If `streaming` is true, we will stream the snapshot data out into four files, using filepath as the prefix, to avoid having to hold the entire snapshot in memory. This option should be used for any setting where your memory is constrained. These files can then be reassembled @@ -1277,28 +1281,28 @@ backwards-compatibility) and your process is killed, note that this will always parts in the same directory as your provided filepath, so you can still reconstruct the snapshot after the fact, via `assemble_snapshot()`. """ -function take_heap_snapshot(filepath::AbstractString, all_one::Bool=false; streaming::Bool=false) +function take_heap_snapshot(filepath::AbstractString, all_one::Bool=false; redact_data::Bool=true, streaming::Bool=false) if streaming - _stream_heap_snapshot(filepath, all_one) + _stream_heap_snapshot(filepath, all_one, redact_data) else # Support the legacy, non-streaming mode, by first streaming the parts, then # reassembling it after we're done. prefix = filepath - _stream_heap_snapshot(prefix, all_one) + _stream_heap_snapshot(prefix, all_one, redact_data) Profile.HeapSnapshot.assemble_snapshot(prefix, filepath) Profile.HeapSnapshot.cleanup_streamed_files(prefix) end return filepath end -function take_heap_snapshot(io::IO, all_one::Bool=false) +function take_heap_snapshot(io::IO, all_one::Bool=false; redact_data::Bool=true) # Support the legacy, non-streaming mode, by first streaming the parts to a tempdir, # then reassembling it after we're done. dir = tempdir() prefix = joinpath(dir, "snapshot") - _stream_heap_snapshot(prefix, all_one) + _stream_heap_snapshot(prefix, all_one, redact_data) Profile.HeapSnapshot.assemble_snapshot(prefix, io) end -function _stream_heap_snapshot(prefix::AbstractString, all_one::Bool) +function _stream_heap_snapshot(prefix::AbstractString, all_one::Bool, redact_data::Bool) # Nodes and edges are binary files open("$prefix.nodes", "w") do nodes open("$prefix.edges", "w") do edges @@ -1311,9 +1315,9 @@ function _stream_heap_snapshot(prefix::AbstractString, all_one::Bool) Base.@_lock_ios(json, ccall(:jl_gc_take_heap_snapshot, Cvoid, - (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}, Cchar), + (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}, Cchar, Cchar), nodes.handle, edges.handle, strings.handle, json.handle, - Cchar(all_one)) + Cchar(all_one), Cchar(redact_data)) ) ) ) @@ -1323,7 +1327,7 @@ function _stream_heap_snapshot(prefix::AbstractString, all_one::Bool) end end end -function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing) where {S <: AbstractString} +function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing, kwargs...) where {S <: AbstractString} fname = "$(getpid())_$(time_ns()).heapsnapshot" if isnothing(dir) wd = pwd() @@ -1338,7 +1342,7 @@ function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing) else fpath = joinpath(expanduser(dir), fname) end - return take_heap_snapshot(fpath, all_one) + return take_heap_snapshot(fpath, all_one; kwargs...) end """ diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 2b1ab546161c6..32d628130c4ac 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -279,16 +279,31 @@ end @testset "HeapSnapshot" begin tmpdir = mktempdir() + + # ensure that we can prevent redacting data fname = cd(tmpdir) do - read(`$(Base.julia_cmd()) --startup-file=no -e "using Profile; print(Profile.take_heap_snapshot())"`, String) + read(`$(Base.julia_cmd()) --startup-file=no -e "using Profile; const x = \"redact_this\"; print(Profile.take_heap_snapshot(; redact_data=false))"`, String) end @test isfile(fname) - open(fname) do fs - @test readline(fs) != "" + sshot = read(fname, String) + @test sshot != "" + @test contains(sshot, "redact_this") + + rm(fname) + + # ensure that string data is redacted by default + fname = cd(tmpdir) do + read(`$(Base.julia_cmd()) --startup-file=no -e "using Profile; const x = \"redact_this\"; print(Profile.take_heap_snapshot())"`, String) end + @test isfile(fname) + + sshot = read(fname, String) + @test sshot != "" + @test !contains(sshot, "redact_this") + rm(fname) rm(tmpdir, force = true, recursive = true) end From 03451ff827aec1bf85113bd63b9fe9d36a55589d Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sun, 25 Aug 2024 18:06:09 -0400 Subject: [PATCH 33/94] Small missing AnnotatedString/Char tests (#55582) Just a few things that coverage shows aren't hit yet --- test/strings/annotated.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/strings/annotated.jl b/test/strings/annotated.jl index c8fa0680113a7..90aaadd6ede24 100644 --- a/test/strings/annotated.jl +++ b/test/strings/annotated.jl @@ -5,6 +5,7 @@ @test str == Base.AnnotatedString(str.string, Tuple{UnitRange{Int}, Pair{Symbol, Any}}[]) @test length(str) == 11 @test ncodeunits(str) == 11 + @test convert(Base.AnnotatedString, str) === str @test eltype(str) == Base.AnnotatedChar{eltype(str.string)} @test first(str) == Base.AnnotatedChar(first(str.string), Pair{Symbol, Any}[]) @test str[1:4] isa SubString{typeof(str)} @@ -63,6 +64,8 @@ end @testset "AnnotatedChar" begin chr = Base.AnnotatedChar('c') + @test Base.AnnotatedChar(UInt32('c')) == chr + @test convert(Base.AnnotatedChar, chr) === chr @test chr == Base.AnnotatedChar(chr.char, Pair{Symbol, Any}[]) @test uppercase(chr) == Base.AnnotatedChar('C') @test titlecase(chr) == Base.AnnotatedChar('C') From 647753071a1e2ddbddf7ab07f55d7146238b6b72 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 25 Aug 2024 19:39:29 -0400 Subject: [PATCH 34/94] prevent stackoverflow of stat/lstat (#55554) Gives a better error message if joinpath does not change types (which will cause stat/lstat to resolve to the same method and crash). Fixes #50890 --- base/stat.jl | 4 ++++ test/file.jl | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/base/stat.jl b/base/stat.jl index 3330ff0c35bc8..506b5644dccbc 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -198,6 +198,7 @@ end """ stat(file) + stat(joinpath...) Return a structure whose fields contain information about the file. The fields of the structure are: @@ -218,16 +219,19 @@ The fields of the structure are: | mtime | `Float64` | Unix timestamp of when the file was last modified | | ctime | `Float64` | Unix timestamp of when the file's metadata was changed | """ +stat(path) = (path2 = joinpath(path); path2 isa typeof(path) ? error("stat not implemented for $(typeof(path))") : stat(path2)) stat(path...) = stat(joinpath(path...)) """ lstat(file) + lstat(joinpath...) Like [`stat`](@ref), but for symbolic links gets the info for the link itself rather than the file it refers to. This function must be called on a file path rather than a file object or a file descriptor. """ +lstat(path) = (path2 = joinpath(path); path2 isa typeof(path) ? error("lstat not implemented for $(typeof(path))") : lstat(path2)) lstat(path...) = lstat(joinpath(path...)) # some convenience functions diff --git a/test/file.jl b/test/file.jl index 005c765e08b90..4531cd8e66998 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1753,8 +1753,18 @@ end @test s.blocks isa Int64 @test s.mtime isa Float64 @test s.ctime isa Float64 + + @test s === stat((f,)) + @test s === lstat((f,)) + @test s === stat(".", f) + @test s === lstat(".", f) end +mutable struct URI50890; f::String; end +Base.joinpath(x::URI50890) = URI50890(x.f) +@test_throws "stat not implemented" stat(URI50890(".")) +@test_throws "lstat not implemented" lstat(URI50890(".")) + @testset "StatStruct show's extended details" begin f, io = mktemp() s = stat(f) From 733b3f55a1daa7afd2a6804e06dba46fc2f2cfa0 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Mon, 26 Aug 2024 19:27:17 -0300 Subject: [PATCH 35/94] Fix cong implementation to be properly random and not just cycling. (#55509) This was found by @IanButterworth. It unfortunately has a small performance regression due to actually using all the rng bits --- src/gc-stock.h | 2 +- src/julia_internal.h | 34 +++++++++++++++++++++++++--------- src/scheduler.c | 2 +- src/signal-handling.c | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/gc-stock.h b/src/gc-stock.h index 7176ad8b504f4..45c93bf4289ae 100644 --- a/src/gc-stock.h +++ b/src/gc-stock.h @@ -446,7 +446,7 @@ STATIC_INLINE int gc_is_concurrent_collector_thread(int tid) JL_NOTSAFEPOINT STATIC_INLINE int gc_random_parallel_collector_thread_id(jl_ptls_t ptls) JL_NOTSAFEPOINT { assert(jl_n_markthreads > 0); - int v = gc_first_tid + (int)cong(jl_n_markthreads - 1, &ptls->rngseed); + int v = gc_first_tid + (int)cong(jl_n_markthreads, &ptls->rngseed); // cong is [0, n) assert(v >= gc_first_tid && v <= gc_last_parallel_collector_thread_id()); return v; } diff --git a/src/julia_internal.h b/src/julia_internal.h index 23a9c90edf8aa..dad28791f8c35 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1307,20 +1307,36 @@ JL_DLLEXPORT size_t jl_maxrss(void); // congruential random number generator // for a small amount of thread-local randomness -STATIC_INLINE uint64_t cong(uint64_t max, uint64_t *seed) JL_NOTSAFEPOINT +//TODO: utilize https://github.com/openssl/openssl/blob/master/crypto/rand/rand_uniform.c#L13-L99 +// for better performance, it does however require making users expect a 32bit random number. + +STATIC_INLINE uint64_t cong(uint64_t max, uint64_t *seed) JL_NOTSAFEPOINT // Open interval [0, max) { - if (max == 0) + if (max < 2) return 0; uint64_t mask = ~(uint64_t)0; - --max; - mask >>= __builtin_clzll(max|1); - uint64_t x; + int zeros = __builtin_clzll(max); + int bits = CHAR_BIT * sizeof(uint64_t) - zeros; + mask = mask >> zeros; do { - *seed = 69069 * (*seed) + 362437; - x = *seed & mask; - } while (x > max); - return x; + uint64_t value = 69069 * (*seed) + 362437; + *seed = value; + uint64_t x = value & mask; + if (x < max) { + return x; + } + int bits_left = zeros; + while (bits_left >= bits) { + value >>= bits; + x = value & mask; + if (x < max) { + return x; + } + bits_left -= bits; + } + } while (1); } + JL_DLLEXPORT uint64_t jl_rand(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_srand(uint64_t) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_init_rand(void); diff --git a/src/scheduler.c b/src/scheduler.c index 3cf97ba108873..71943a25f3233 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -87,7 +87,7 @@ extern int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, // parallel task runtime // --- -JL_DLLEXPORT uint32_t jl_rand_ptls(uint32_t max) +JL_DLLEXPORT uint32_t jl_rand_ptls(uint32_t max) // [0, n) { jl_ptls_t ptls = jl_current_task->ptls; return cong(max, &ptls->rngseed); diff --git a/src/signal-handling.c b/src/signal-handling.c index 6835f5fa364c5..d7f4697a3c4f0 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -155,7 +155,7 @@ static void jl_shuffle_int_array_inplace(int *carray, int size, uint64_t *seed) // The "modern Fisher–Yates shuffle" - O(n) algorithm // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm for (int i = size; i-- > 1; ) { - size_t j = cong(i, seed); + size_t j = cong(i + 1, seed); // cong is an open interval so we add 1 uint64_t tmp = carray[j]; carray[j] = carray[i]; carray[i] = tmp; From 78b0b74048dd64e742ca28e52994f449ba67c259 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:29:03 +0900 Subject: [PATCH 36/94] inference: refine slot undef info within `then` branch of `@isdefined` (#55545) By adding some information to `Conditional`, it is possible to improve the `undef` information of `slot` within the `then` branch of `@isdefined slot`. As a result, it's now possible to prove the `:nothrow`-ness in cases like: ```julia @test Base.infer_effects((Bool,Int)) do c, x local val if c val = x end if @isdefined val return val end return zero(Int) end |> Core.Compiler.is_nothrow ``` --- base/compiler/abstractinterpretation.jl | 16 ++++++++------ base/compiler/typelattice.jl | 28 ++++++++++++++++++------- test/compiler/inference.jl | 12 +++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 26bba7b51a2dd..f3fc4e0423173 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2733,6 +2733,8 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U rt = Const(false) # never assigned previously elseif !vtyp.undef rt = Const(true) # definitely assigned previously + else # form `Conditional` to refine `vtyp.undef` in the then branch + rt = Conditional(sym, vtyp.typ, vtyp.typ; isdefined=true) end elseif isa(sym, GlobalRef) if InferenceParams(interp).assume_bindings_static @@ -3205,7 +3207,7 @@ end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) - changes = StateUpdate(stmt.slot, VarState(Bottom, true), false) + changes = StateUpdate(stmt.slot, VarState(Bottom, true)) return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) @@ -3220,7 +3222,7 @@ end end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(rt, false), false) + changes = StateUpdate(lhs, VarState(rt, false)) elseif isa(lhs, GlobalRef) handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) @@ -3230,7 +3232,7 @@ end elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) - changes = StateUpdate(fname, VarState(Any, false), false) + changes = StateUpdate(fname, VarState(Any, false)) end return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( @@ -3576,7 +3578,7 @@ function apply_refinement!(𝕃ᵢ::AbstractLattice, slot::SlotNumber, @nospecia oldtyp = vtype.typ ⊏ = strictpartialorder(𝕃ᵢ) if newtyp ⊏ oldtyp - stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef), false) + stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef)) stoverwrite1!(currstate, stmtupdate) end end @@ -3600,7 +3602,9 @@ function conditional_change(𝕃ᵢ::AbstractLattice, currstate::VarTable, condt # "causes" since we ignored those in the comparison newtyp = tmerge(𝕃ᵢ, newtyp, LimitedAccuracy(Bottom, oldtyp.causes)) end - return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, vtype.undef), true) + # if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` information + newundef = condt.isdefined ? !then_or_else : vtype.undef + return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, newundef), #=conditional=#true) end function condition_object_change(currstate::VarTable, condt::Conditional, @@ -3609,7 +3613,7 @@ function condition_object_change(currstate::VarTable, condt::Conditional, newcondt = Conditional(condt.slot, then_or_else ? condt.thentype : Union{}, then_or_else ? Union{} : condt.elsetype) - return StateUpdate(condslot, VarState(newcondt, vtype.undef), false) + return StateUpdate(condslot, VarState(newcondt, vtype.undef)) end # make as much progress on `frame` as possible (by handling cycles) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 14477c5dc2725..86fa8af21615f 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -73,14 +73,19 @@ struct Conditional slot::Int thentype elsetype - function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) + # `isdefined` indicates this `Conditional` is from `@isdefined slot`, implying that + # the `undef` information of `slot` can be improved in the then branch. + # Since this is only beneficial for local inference, it is not translated into `InterConditional`. + isdefined::Bool + function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype); + isdefined::Bool=false) assert_nested_slotwrapper(thentype) assert_nested_slotwrapper(elsetype) - return new(slot, thentype, elsetype) + return new(slot, thentype, elsetype, isdefined) end end -Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = - Conditional(slot_id(var), thentype, elsetype) +Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) = + Conditional(slot_id(var), thentype, elsetype; isdefined) import Core: InterConditional """ @@ -180,6 +185,7 @@ struct StateUpdate var::SlotNumber vtype::VarState conditional::Bool + StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional) end """ @@ -305,11 +311,17 @@ end # `Conditional` and `InterConditional` are valid in opposite contexts # (i.e. local inference and inter-procedural call), as such they will never be compared -@nospecializeinfer function issubconditional(lattice::AbstractLattice, a::C, b::C) where {C<:AnyConditional} +@nospecializeinfer issubconditional(𝕃::AbstractLattice, a::Conditional, b::Conditional) = + _issubconditional(𝕃, a, b, #=check_isdefined=#true) +@nospecializeinfer issubconditional(𝕃::AbstractLattice, a::InterConditional, b::InterConditional) = + _issubconditional(𝕃, a, b, #=check_isdefined=#false) +@nospecializeinfer function _issubconditional(𝕃::AbstractLattice, a::C, b::C, check_isdefined::Bool) where C<:AnyConditional if is_same_conditionals(a, b) - if ⊑(lattice, a.thentype, b.thentype) - if ⊑(lattice, a.elsetype, b.elsetype) - return true + if ⊑(𝕃, a.thentype, b.thentype) + if ⊑(𝕃, a.elsetype, b.elsetype) + if !check_isdefined || a.isdefined ≥ b.isdefined + return true + end end end end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 6e2fa77eb15c8..7cb97db7b9cf4 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6050,6 +6050,18 @@ end |> Core.Compiler.is_nothrow return nothing end |> Core.Compiler.is_nothrow +# refine `undef` information from `@isdefined` check +@test Base.infer_effects((Bool,Int)) do c, x + local val + if c + val = x + end + if @isdefined val + return val + end + return zero(Int) +end |> Core.Compiler.is_nothrow + # End to end test case for the partially initialized struct with `PartialStruct` @noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) @test fully_eliminated() do From f457a7561526aa28e20c4b2a8ff1c650f0c0f910 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 27 Aug 2024 12:25:52 -0400 Subject: [PATCH 37/94] Try to remove likely-unused codepath in codegen for :method (#55532) This codepath is odd. It derives a method name from the slotname metadata and then does an implicit assignment to that slot. Worse, as the comment indicates, the codepath is missing from the interpreter, which will crash if it were to ever encounter such a piece of code. I suspect this pattern is unused - I accidentally broke it badly in (the as of yet unmerged PR) #54788 and neither tests nor pkgeval noticed. So let's try removing it on master. If it turns out something does depend on it, we can go the opposite way and implement it properly in all the places that look at :method. --- src/codegen.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index e9a58d25e3e94..e499d1193dee6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6412,13 +6412,6 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); } - else if (jl_is_slotnumber(mn) || jl_is_argument(mn)) { - // XXX: eval_methoddef does not have this code branch - int sl = jl_slot_number(mn)-1; - jl_varinfo_t &vi = ctx.slots[sl]; - bp = vi.boxroot; - name = literal_pointer_val(ctx, (jl_value_t*)slot_symbol(ctx, sl)); - } if (bp) { Value *mdargs[] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, literal_pointer_val(ctx, bnd) }; jl_cgval_t gf = mark_julia_type( From d5bbcc5aaec8033de1dd5b4b0de730ab40575469 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 27 Aug 2024 15:00:55 -0300 Subject: [PATCH 38/94] Initialize threadpools correctly during sysimg build (#55567) I made a mistake with which threadpool was which. --- src/init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init.c b/src/init.c index 9e6a695c71eb0..1d466a0a736f9 100644 --- a/src/init.c +++ b/src/init.c @@ -875,8 +875,8 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_n_markthreads = 0; jl_n_sweepthreads = 0; jl_n_gcthreads = 0; - jl_n_threads_per_pool[0] = 1; - jl_n_threads_per_pool[1] = 0; + jl_n_threads_per_pool[0] = 0; // Interactive threadpool + jl_n_threads_per_pool[1] = 1; // Default threadpool } else { post_image_load_hooks(); } From 688811d73fb24f5f91a6d01f445207042b991a79 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 27 Aug 2024 14:15:14 -0400 Subject: [PATCH 39/94] re-enable profiling stack switch test (#55553) Removes the warning on platforms where CFI_NORETURN appears likely to be sufficient alone for this to work (from observation in gdb/lldb) and re-enables the test on all platforms so we can see if more work here is needed at all (e.g. similar annotations in jl_setjmp/jl_longjmp). Refs #43124 --- src/task.c | 8 ++++++-- test/threads.jl | 23 ++++++++--------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/task.c b/src/task.c index a88f3b55fc419..86acac23a186a 100644 --- a/src/task.c +++ b/src/task.c @@ -223,6 +223,7 @@ JL_NO_ASAN static void NOINLINE JL_NORETURN restore_stack(jl_ucontext_t *t, jl_p } void *_y = t->stkbuf; assert(_x != NULL && _y != NULL); +#if defined(_OS_WINDOWS_) // this platform does not implement CFI_NORETURN correctly or at all in libunwind (or equivalent) which requires a workaround #if defined(_CPU_X86_) || defined(_CPU_X86_64_) void *volatile *return_address = (void *volatile *)__builtin_frame_address(0) + 1; assert(*return_address == __builtin_return_address(0)); @@ -230,7 +231,9 @@ JL_NO_ASAN static void NOINLINE JL_NORETURN restore_stack(jl_ucontext_t *t, jl_p #else #pragma message("warning: CFI_NORETURN not implemented for this platform, so profiling of copy_stacks may segfault in this build") #endif +#else CFI_NORETURN +#endif memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe #if defined(_OS_WINDOWS_) @@ -287,14 +290,15 @@ JL_NO_ASAN static void NOINLINE restore_stack3(jl_ucontext_t *t, jl_ptls_t ptls, restore_stack3(t, ptls, p); // pass p to ensure the compiler can't tailcall this or avoid the alloca } #endif +#if defined(_OS_WINDOWS_) // this platform does not implement CFI_NORETURN correctly or at all in libunwind (or equivalent) which requires a workaround #if defined(_CPU_X86_) || defined(_CPU_X86_64_) void *volatile *return_address = (void *volatile *)__builtin_frame_address(0) + 1; assert(*return_address == __builtin_return_address(0)); *return_address = NULL; -#else -#pragma message("warning: CFI_NORETURN not implemented for this platform, so profiling of copy_stacks may segfault in this build") #endif +#else CFI_NORETURN +#endif tsan_switch_to_ctx(t); jl_start_fiber_set(t); // (doesn't return) abort(); diff --git a/test/threads.jl b/test/threads.jl index 7b4558091022b..2832f2a0e972c 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -288,18 +288,16 @@ close(proc.in) proc = run(cmd; wait = false) done = Threads.Atomic{Bool}(false) timeout = false - timer = Timer(100) do _ + timer = Timer(200) do _ timeout = true - for sig in [Base.SIGTERM, Base.SIGHUP, Base.SIGKILL] - for _ in 1:1000 + for sig in (Base.SIGQUIT, Base.SIGKILL) + for _ in 1:3 kill(proc, sig) + sleep(1) if done[] - if sig != Base.SIGTERM - @warn "Terminating `$script` required signal $sig" - end + @warn "Terminating `$script` required signal $sig" return end - sleep(0.001) end end end @@ -309,16 +307,11 @@ close(proc.in) done[] = true close(timer) end - if ( !success(proc) ) || ( timeout ) + if !success(proc) || timeout @error "A \"spawn and wait lots of tasks\" test failed" n proc.exitcode proc.termsignal success(proc) timeout end - if Sys.iswindows() || Sys.isapple() - # Known failure: https://github.com/JuliaLang/julia/issues/43124 - @test_skip success(proc) - else - @test success(proc) - @test !timeout - end + @test success(proc) + @test !timeout end end From 16697f369077011d1d694414fc6ced7276a452e5 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Tue, 27 Aug 2024 13:49:19 -0700 Subject: [PATCH 40/94] Downgrade patchelf to v0.17.2 (#55602) This should fix https://github.com/JuliaLang/julia/issues/55423 --- deps/checksums/patchelf | 4 ++-- deps/patchelf.version | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deps/checksums/patchelf b/deps/checksums/patchelf index e2029b83f14fc..6392e44d8f2e8 100644 --- a/deps/checksums/patchelf +++ b/deps/checksums/patchelf @@ -1,2 +1,2 @@ -patchelf-0.18.0.tar.bz2/md5/9b091a689583fdc7c3206679586322d5 -patchelf-0.18.0.tar.bz2/sha512/bf26194ca3435b141dd330890fcc0c9d805d0ad6a537901dabe6707a13cd28e7e6217462f3ebb3cb4861302dd8632342ec988fc18246c35332a94f2b349d4f4f +patchelf-0.17.2.tar.bz2/md5/d76db4f1a27b0934d0b0d0585b081c0f +patchelf-0.17.2.tar.bz2/sha512/8277adf95513f88fb190536a38bdfdf438a4cc7685d8a130bdffbe064441f0f25095b6c83bbb190133e1a138963776d15b46c247dd2f1a073a1bfe1d1dbdd503 diff --git a/deps/patchelf.version b/deps/patchelf.version index 9038338d45faf..6e4f32a0c2fe4 100644 --- a/deps/patchelf.version +++ b/deps/patchelf.version @@ -1,3 +1,4 @@ ## source build # Patchelf (we don't ship this or even use a JLL, we just always build it) -PATCHELF_VER := 0.18.0 +# NOTE: Do not upgrade this to 0.18+ until https://github.com/NixOS/patchelf/issues/492 is fixed +PATCHELF_VER := 0.17.2 From 878f62113d488dd3e742d68784f46a9c5a163c11 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Wed, 28 Aug 2024 12:41:45 +0530 Subject: [PATCH 41/94] Convert triangular inv test comparison to isapprox (#55609) The failing test was noticed in https://github.com/JuliaLang/julia/pull/55607. The comparison will only hold approximately in general, as the inverses are computed differently. --- stdlib/LinearAlgebra/test/triangular.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index a9aeab5650f44..5f0a829f9cdda 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -903,7 +903,7 @@ end function test_one_oneunit_triangular(a) b = Matrix(a) @test (@inferred a^1) == b^1 - @test (@inferred a^-1) == b^-1 + @test (@inferred a^-1) ≈ b^-1 @test one(a) == one(b) @test one(a)*a == a @test a*one(a) == a From d882d622e920fab62f0e6fbaddaec87aefb53236 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 28 Aug 2024 04:22:55 -0300 Subject: [PATCH 42/94] Use native tls model in macos for better performance (#55576) Macos has a native tls implementation in clang since at least clang 3.7 which much older than what we require so lets enable it for some small performance gains. We may want to turn on the ifunc optimization that's there as well but I haven't tested it and it's only in clang 18 and up so it would be off for most since Apple clang is 16 on their latest beta https://github.com/llvm/llvm-project/pull/73687 --- cli/loader_lib.c | 2 +- src/gc-stacks.c | 2 +- src/julia_fasttls.h | 7 +------ src/julia_internal.h | 4 +--- src/scheduler.c | 15 +++++++++------ src/threading.c | 43 +++++-------------------------------------- 6 files changed, 18 insertions(+), 55 deletions(-) diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 65a5e7621a714..af2a36cfce8ab 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -546,7 +546,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { (*jl_codegen_exported_func_addrs[symbol_idx]) = addr; } // Next, if we're on Linux/FreeBSD, set up fast TLS. -#if !defined(_OS_WINDOWS_) && !defined(_OS_DARWIN_) && !defined(_OS_OPENBSD_) +#if !defined(_OS_WINDOWS_) && !defined(_OS_OPENBSD_) void (*jl_pgcstack_setkey)(void*, void*(*)(void)) = lookup_symbol(libjulia_internal, "jl_pgcstack_setkey"); if (jl_pgcstack_setkey == NULL) { jl_loader_print_stderr("ERROR: Cannot find jl_pgcstack_setkey() function within libjulia-internal!\n"); diff --git a/src/gc-stacks.c b/src/gc-stacks.c index 65173d3a8df37..08425019a4daf 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -160,7 +160,7 @@ static unsigned select_pool(size_t nb) JL_NOTSAFEPOINT } -static void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT +void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT { #ifdef _COMPILER_ASAN_ENABLED_ __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); diff --git a/src/julia_fasttls.h b/src/julia_fasttls.h index 1c0929717b293..1f35d3693fefd 100644 --- a/src/julia_fasttls.h +++ b/src/julia_fasttls.h @@ -22,14 +22,9 @@ extern "C" { typedef struct _jl_gcframe_t jl_gcframe_t; -#if defined(_OS_DARWIN_) -#include -typedef void *(jl_get_pgcstack_func)(pthread_key_t); // aka typeof(pthread_getspecific) -#else typedef jl_gcframe_t **(jl_get_pgcstack_func)(void); -#endif -#if !defined(_OS_DARWIN_) && !defined(_OS_WINDOWS_) +#if !defined(_OS_WINDOWS_) #define JULIA_DEFINE_FAST_TLS \ static __attribute__((tls_model("local-exec"))) __thread jl_gcframe_t **jl_pgcstack_localexec; \ JL_DLLEXPORT _Atomic(char) jl_pgcstack_static_semaphore; \ diff --git a/src/julia_internal.h b/src/julia_internal.h index dad28791f8c35..8ea1940224e66 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1006,9 +1006,7 @@ int jl_safepoint_consume_sigint(void); void jl_wake_libuv(void) JL_NOTSAFEPOINT; void jl_set_pgcstack(jl_gcframe_t **) JL_NOTSAFEPOINT; -#if defined(_OS_DARWIN_) -typedef pthread_key_t jl_pgcstack_key_t; -#elif defined(_OS_WINDOWS_) +#if defined(_OS_WINDOWS_) typedef DWORD jl_pgcstack_key_t; #else typedef jl_gcframe_t ***(*jl_pgcstack_key_t)(void) JL_NOTSAFEPOINT; diff --git a/src/scheduler.c b/src/scheduler.c index 71943a25f3233..bd7da13aa42e3 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -253,10 +253,7 @@ static void wake_libuv(void) JL_NOTSAFEPOINT JULIA_DEBUG_SLEEPWAKE( io_wakeup_leave = cycleclock() ); } -/* ensure thread tid is awake if necessary */ -JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT -{ - jl_task_t *ct = jl_current_task; +void wakeup_thread(jl_task_t *ct, int16_t tid) JL_NOTSAFEPOINT { // Pass in ptls when we have it already available to save a lookup int16_t self = jl_atomic_load_relaxed(&ct->tid); if (tid != self) jl_fence(); // [^store_buffering_1] @@ -311,6 +308,12 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT JULIA_DEBUG_SLEEPWAKE( wakeup_leave = cycleclock() ); } +/* ensure thread tid is awake if necessary */ +JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT +{ + jl_task_t *ct = jl_current_task; + wakeup_thread(ct, tid); +} // get the next runnable task static jl_task_t *get_next_task(jl_value_t *trypoptask, jl_value_t *q) @@ -447,7 +450,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, // responsibility, so need to make sure thread 0 will take care // of us. if (jl_atomic_load_relaxed(&jl_uv_mutex.owner) == NULL) // aka trylock - jl_wakeup_thread(0); + wakeup_thread(ct, 0); } if (uvlock) { int enter_eventloop = may_sleep(ptls); @@ -575,7 +578,7 @@ void scheduler_delete_thread(jl_ptls_t ptls) JL_NOTSAFEPOINT else { jl_atomic_fetch_add_relaxed(&n_threads_running, 1); } - jl_wakeup_thread(0); // force thread 0 to see that we do not have the IO lock (and am dead) + wakeup_thread(jl_atomic_load_relaxed(&ptls->current_task), 0); // force thread 0 to see that we do not have the IO lock (and am dead) jl_atomic_fetch_add_relaxed(&n_threads_running, -1); } diff --git a/src/threading.c b/src/threading.c index 8f37ee814056c..a6050ace01833 100644 --- a/src/threading.c +++ b/src/threading.c @@ -82,51 +82,17 @@ JL_DLLEXPORT void jl_set_safe_restore(jl_jmp_buf *sr) // The tls_states buffer: // // On platforms that do not use ELF (i.e. where `__thread` is emulated with -// lower level API) (Mac, Windows), we use the platform runtime API to create +// lower level API) (Windows), we use the platform runtime API to create // TLS variable directly. // This is functionally equivalent to using `__thread` but can be // more efficient since we can have better control over the creation and // initialization of the TLS buffer. // -// On platforms that use ELF (Linux, FreeBSD), we use a `__thread` variable +// On platforms that support native TLS (ELF platforms + Macos) we use a `__thread` variable // as the fallback in the shared object. For better efficiency, we also // create a `__thread` variable in the main executable using a static TLS // model. -#if defined(_OS_DARWIN_) -// Mac doesn't seem to have static TLS model so the runtime TLS getter -// registration will only add overhead to TLS access. The `__thread` variables -// are emulated with `pthread_key_t` so it is actually faster to use it directly. -static pthread_key_t jl_pgcstack_key; - -__attribute__((constructor)) void jl_init_tls(void) -{ - pthread_key_create(&jl_pgcstack_key, NULL); -} - -JL_CONST_FUNC jl_gcframe_t **jl_get_pgcstack(void) JL_NOTSAFEPOINT -{ - return (jl_gcframe_t**)pthread_getspecific(jl_pgcstack_key); -} - -void jl_set_pgcstack(jl_gcframe_t **pgcstack) JL_NOTSAFEPOINT -{ - pthread_setspecific(jl_pgcstack_key, (void*)pgcstack); -} - -void jl_pgcstack_getkey(jl_get_pgcstack_func **f, pthread_key_t *k) -{ - // for codegen - *f = pthread_getspecific; - *k = jl_pgcstack_key; -} - - -JL_DLLEXPORT void jl_pgcstack_setkey(jl_get_pgcstack_func *f, pthread_key_t k) -{ - jl_safe_printf("ERROR: Attempt to change TLS address.\n"); -} - -#elif defined(_OS_WINDOWS_) +#if defined(_OS_WINDOWS_) // Apparently windows doesn't have a static TLS model (or one that can be // reliably used from a shared library) either..... Use `TLSAlloc` instead. @@ -464,6 +430,7 @@ void jl_safepoint_resume_all_threads(jl_task_t *ct) void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; void scheduler_delete_thread(jl_ptls_t ptls) JL_NOTSAFEPOINT; +void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT; static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER { @@ -492,7 +459,7 @@ static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER } if (signal_stack != NULL) { if (signal_stack_size) - jl_free_stack(signal_stack, signal_stack_size); + _jl_free_stack(ptls ,signal_stack, signal_stack_size); else free(signal_stack); } From 644029282cfeafcb3b80d999b0054cdaa9278e3d Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Wed, 28 Aug 2024 14:05:58 +0530 Subject: [PATCH 43/94] Fast bounds-check for CartesianIndex ranges (#55596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `StepRangeLen{<:CartesianIndex}` indices have been supported since v1.11, but bounds-checking for such indices currently falls back to iterating over the entire range. This PR adds a quick `checkindex` for such ranges. The performance improvement as a consequence: ```julia julia> D = Diagonal(1:10_000); julia> @btime checkbounds($D, diagind($D, IndexCartesian())); 6.697 μs (0 allocations: 0 bytes) # nightly, O(n) 4.044 ns (0 allocations: 0 bytes) # This PR, O(1) ``` --- base/multidimensional.jl | 2 ++ stdlib/LinearAlgebra/test/diagonal.jl | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 5e32a19c2cafb..99f41f2404e47 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -730,6 +730,8 @@ end end @inline checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndex) = checkbounds_indices(Bool, inds, I.I) +@inline checkindex(::Type{Bool}, inds::Tuple, i::AbstractRange{<:CartesianIndex}) = + isempty(i) | (checkindex(Bool, inds, first(i)) & checkindex(Bool, inds, last(i))) # Indexing into Array with mixtures of Integers and CartesianIndices is # extremely performance-sensitive. While the abstract fallbacks support this, diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index afb49b696d968..866c11b9931cd 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -1354,4 +1354,9 @@ end end end +@testset "bounds-check with CartesianIndex ranges" begin + D = Diagonal(1:typemax(Int)) + @test checkbounds(Bool, D, diagind(D, IndexCartesian())) +end + end # module TestDiagonal From ec2d6960050eb482b581023d9dcbeb6b11b92f29 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 28 Aug 2024 08:33:17 -0400 Subject: [PATCH 44/94] Test more UTF-8 characters in transcode (#55580) Previous test just covered 2-byte UTF8. This one should hit more branches for ASCII and 3-byte UTF-8. --- test/strings/basic.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 511b498e5cd89..a7266f52f16fc 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -1392,8 +1392,15 @@ end end @testset "transcode" begin - str = "αβγ" - @test transcode(String, transcode(UInt16, str)) == str - @test transcode(String, transcode(UInt16, transcode(UInt8, str))) == str - @test transcode(String, transcode(UInt8, transcode(UInt16, str))) == str + # string starting with an ASCII character + str_1 = "zβγ" + # string starting with a 2 byte UTF-8 character + str_2 = "αβγ" + # string starting with a 3 byte UTF-8 character + str_3 = "आख" + @testset for str in (str_1, str_2, str_3) + @test transcode(String, transcode(UInt16, str)) == str + @test transcode(String, transcode(UInt16, transcode(UInt8, str))) == str + @test transcode(String, transcode(UInt8, transcode(UInt16, str))) == str + end end From 378f1920db0243224fbea8aea4e69ce36cd85173 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:58:17 -0400 Subject: [PATCH 45/94] precompile: Ensure CI has inference results available for `jl_create_native` (#55528) `jl_emit_codeinst` expects inference results to be available, so `jl_ci_cache_lookup` needs to provide a CI that has them. I noticed this because it was causing missing code when, e.g., compiling `NetworkOptions.__init__` using `--trim` (#55047). Unfortunately `jl_emit_codeinst` failures are silently ignored (they just leave the LLVM module empty), so it was easy to miss that the looked-up CIs sometimes fail to compile. --- src/aotcompile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 48afa360c0037..b4c8ef6095a55 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -284,7 +284,7 @@ jl_code_instance_t *jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_ jl_value_t *ci = cgparams.lookup(mi, world, world); JL_GC_PROMISE_ROOTED(ci); jl_code_instance_t *codeinst = NULL; - if (ci != jl_nothing) { + if (ci != jl_nothing && jl_atomic_load_relaxed(&((jl_code_instance_t *)ci)->inferred) != jl_nothing) { codeinst = (jl_code_instance_t*)ci; } else { From 2ce69eaa6c19046e59e9479b737813ce47b89b61 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 28 Aug 2024 22:45:34 -0400 Subject: [PATCH 46/94] Make jl_static_show_func_sig more robust (#55620) Fixes a crash in existing compiler tests when build with debug symbols. More generally, this function is used for C-side debugging so should be robust to unexpected data. (In this particular case the signature in question was a plain `Tuple{Any}` from the opaque closure test). --- src/rtutils.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rtutils.c b/src/rtutils.c index 4a2e5c230883e..a60597827b92c 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -1403,6 +1403,7 @@ size_t jl_static_show_func_sig_(JL_STREAM *s, jl_value_t *type, jl_static_show_c return n; } if ((jl_nparams(ftype) == 0 || ftype == ((jl_datatype_t*)ftype)->name->wrapper) && + ((jl_datatype_t*)ftype)->name->mt && ((jl_datatype_t*)ftype)->name->mt != jl_type_type_mt && ((jl_datatype_t*)ftype)->name->mt != jl_nonfunction_mt) { n += jl_static_show_symbol(s, ((jl_datatype_t*)ftype)->name->mt->name); From a732dc30f1d1a7f27ab7aa75465e8306ec7870b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20=C5=BBygie=C5=82o?= <11896137+pzygielo@users.noreply.github.com> Date: Thu, 29 Aug 2024 04:49:55 +0200 Subject: [PATCH 47/94] Synchronize arg name with description and --help (#55619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Piotrek Żygieło --- doc/man/julia.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/man/julia.1 b/doc/man/julia.1 index 049543d795acd..ebac4362b39a6 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -28,7 +28,7 @@ julia - a high-level, high-performance dynamic programming language for technical computing .SH SYNOPSIS -\fBjulia\fR [OPTIONS...] \fB--\fR [PROGRAMMFILE] [ARGS...] +\fBjulia\fR [OPTIONS...] \fB--\fR [PROGRAMFILE] [ARGS...] If a Julia source file is given as a \fIPROGRAMFILE\fP (optionally followed by arguments in \fIARGS\fP) Julia will execute the program and exit. From 950a87f15bb7da9eb383c025ec1bd5ab550620f0 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Thu, 29 Aug 2024 01:44:49 -0400 Subject: [PATCH 48/94] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20Pk?= =?UTF-8?q?g=20stdlib=20from=20d1d2fc986=20to=2043e7849ce=20(#55618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stdlib: Pkg URL: https://github.com/JuliaLang/Pkg.jl.git Stdlib branch: master Julia branch: master Old commit: d1d2fc986 New commit: 43e7849ce Julia version: 1.12.0-DEV Pkg version: 1.12.0 Bump invoked by: @IanButterworth Powered by: [BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl) Diff: https://github.com/JuliaLang/Pkg.jl/compare/d1d2fc986e7249909b450979acc4d359aacfc88e...43e7849ce37545493d0da3226cd7449f5f88563e ``` $ git log --oneline d1d2fc986..43e7849ce 43e7849ce make some test_logs match any because of new Downloads debugs (#4007) 8b2c0f329 Better error messages if artifact rename fails (#3827) ``` Co-authored-by: Dilum Aluthge --- .../Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 | 1 + .../Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 | 1 + .../Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/md5 | 1 - .../Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/sha512 | 1 - stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 create mode 100644 deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 delete mode 100644 deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/sha512 diff --git a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 new file mode 100644 index 0000000000000..2d5f5888e777f --- /dev/null +++ b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 @@ -0,0 +1 @@ +d992a5c629199747d68baa1593a7c37d diff --git a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 new file mode 100644 index 0000000000000..4201ee05347a7 --- /dev/null +++ b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 @@ -0,0 +1 @@ +27ea738dbc4db8e4a02b00fbbdc4e2047906fe0561dd4c7f9e5ce5ea9b0b7b8ef9e29234f8e435deaa6cb3e29861130b06cb0da11118c40d78f4c475ac48db3f diff --git a/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/md5 b/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/md5 deleted file mode 100644 index 097013569ceae..0000000000000 --- a/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -725181b382adb22ad4f1f5e78db526ed diff --git a/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/sha512 b/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/sha512 deleted file mode 100644 index d6d8155431023..0000000000000 --- a/deps/checksums/Pkg-d1d2fc986e7249909b450979acc4d359aacfc88e.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -9ab56f368d5075a6f514ab8d2169239b439610c9bc9aca67a45a8a834b4d4ae7988910de3c78a687e40623fcd8bc9ba4aeee64ae7edf2cc84f1945b7e543a559 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index cc38c67021224..60d2914b7f853 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = d1d2fc986e7249909b450979acc4d359aacfc88e +PKG_SHA1 = 43e7849ce37545493d0da3226cd7449f5f88563e PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From ac0161a67b421e2016d3c6759c486a633a1c70a5 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:51:21 +0900 Subject: [PATCH 49/94] optimizer: don't insert `:throw_undef_if_not` for defined slots (#55600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As an application of JuliaLang/julia#55545, this commit avoids the insertion of `:throw_undef_if_not` nodes when the defined-ness of a slot is guaranteed by abstract interpretation. ```julia julia> function isdefined_nothrow(c, x) local val if c val = x end if @isdefined val return val end return zero(Int) end; julia> @code_typed isdefined_nothrow(true, 42) ``` ```diff diff --git a/old b/new index c4980a5c9c..3d1d6d30f0 100644 --- a/old +++ b/new @@ -4,7 +4,6 @@ CodeInfo( 3 ┄ %3 = φ (#2 => x, #1 => #undef)::Int64 │ %4 = φ (#2 => true, #1 => false)::Bool └── goto #5 if not %4 -4 ─ $(Expr(:throw_undef_if_not, :val, :(%4)))::Any -└── return %3 +4 ─ return %3 5 ─ return 0 ) => Int64 ``` --- base/compiler/ssair/slot2ssa.jl | 9 ++++++--- test/compiler/inference.jl | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 756dc98863af5..e70633ffecf6a 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -88,6 +88,9 @@ function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, @nospecializ insert_node!(ir, idx, NewInstruction( Expr(:throw_undef_if_not, ci.slotnames[slot], false), Any)) return UNDEF_TOKEN + elseif has_flag(ir.stmts[idx], IR_FLAG_NOTHROW) + # if the `isdefined`-ness of this slot is guaranteed by abstract interpretation, + # there is no need to form a `:throw_undef_if_not` elseif def_ssa !== true insert_node!(ir, idx, NewInstruction( Expr(:throw_undef_if_not, ci.slotnames[slot], def_ssa), Any)) @@ -153,12 +156,12 @@ end function fixup_uses!(ir::IRCode, ci::CodeInfo, code::Vector{Any}, uses::Vector{Int}, slot::Int, @nospecialize(ssa)) for use in uses - code[use] = fixemup!(x::SlotNumber->slot_id(x)==slot, stmt::SlotNumber->(ssa, true), ir, ci, use, code[use]) + code[use] = fixemup!(x::SlotNumber->slot_id(x)==slot, ::SlotNumber->Pair{Any,Any}(ssa, true), ir, ci, use, code[use]) end end function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), renames::Vector{Pair{Any, Any}}) - return fixemup!(stmt::SlotNumber->true, stmt::SlotNumber->renames[slot_id(stmt)], ir, ci, idx, stmt) + return fixemup!(::SlotNumber->true, x::SlotNumber->renames[slot_id(x)], ir, ci, idx, stmt) end # maybe use expr_type? @@ -656,7 +659,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, visited = BitSet() new_nodes = ir.new_nodes @timeit "SSA Rename" while !isempty(worklist) - (item::Int, pred, incoming_vals) = pop!(worklist) + (item, pred, incoming_vals) = pop!(worklist) if sv.bb_vartables[item] === nothing continue end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 7cb97db7b9cf4..485ee579abd52 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6051,7 +6051,7 @@ end |> Core.Compiler.is_nothrow end |> Core.Compiler.is_nothrow # refine `undef` information from `@isdefined` check -@test Base.infer_effects((Bool,Int)) do c, x +function isdefined_nothrow(c, x) local val if c val = x @@ -6060,7 +6060,11 @@ end |> Core.Compiler.is_nothrow return val end return zero(Int) -end |> Core.Compiler.is_nothrow +end +@test Core.Compiler.is_nothrow(Base.infer_effects(isdefined_nothrow, (Bool,Int))) +@test !any(first(only(code_typed(isdefined_nothrow, (Bool,Int)))).code) do @nospecialize x + Meta.isexpr(x, :throw_undef_if_not) +end # End to end test case for the partially initialized struct with `PartialStruct` @noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) From cebfd7bc66153b82c56715cb1cb52dac7df8eac8 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:34:00 -0400 Subject: [PATCH 50/94] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20Do?= =?UTF-8?q?wnloads=20stdlib=20from=20a9d274f=20to=201061ecc=20(#55614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/Downloads.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/md5 create mode 100644 deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/sha512 delete mode 100644 deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 delete mode 100644 deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 diff --git a/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/md5 b/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/md5 new file mode 100644 index 0000000000000..f42bbedb6d415 --- /dev/null +++ b/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/md5 @@ -0,0 +1 @@ +70878dd96911d6960537dfee2a820d98 diff --git a/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/sha512 b/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/sha512 new file mode 100644 index 0000000000000..83164cad9a89d --- /dev/null +++ b/deps/checksums/Downloads-1061ecc377a053fce0df94e1a19e5260f7c030f5.tar.gz/sha512 @@ -0,0 +1 @@ +87d2bdc6c85cbbce5302aab8ffe92fc542c9c71a396844fcc04c0416be059b00298b4816ab5e5491dbf865660a3a6152f1c245875a1ec75fb49b4c7ba0d303d8 diff --git a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 deleted file mode 100644 index fc3bce951cafb..0000000000000 --- a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -97bb33510fadec7f4cc4c718c739e9a0 diff --git a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 deleted file mode 100644 index bf2821e8252b0..0000000000000 --- a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -a362aaf762f42deebb8632a7a7980cd22b2777e8c4dc629e418580269e24a64217ad846d61acad70438cfdc190e47ba2ff7716edd4e04d8d10c1d765efce604d diff --git a/stdlib/Downloads.version b/stdlib/Downloads.version index 7805348a4b2f5..cb041d86d7f66 100644 --- a/stdlib/Downloads.version +++ b/stdlib/Downloads.version @@ -1,4 +1,4 @@ DOWNLOADS_BRANCH = master -DOWNLOADS_SHA1 = a9d274ff6588cc5dbfa90e908ee34c2408bab84a +DOWNLOADS_SHA1 = 1061ecc377a053fce0df94e1a19e5260f7c030f5 DOWNLOADS_GIT_URL := https://github.com/JuliaLang/Downloads.jl.git DOWNLOADS_TAR_URL = https://api.github.com/repos/JuliaLang/Downloads.jl/tarball/$1 From 4f1af7f39d05a6c2babafae8c5b1f122ee768604 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:59:19 -0400 Subject: [PATCH 51/94] Allow opting out of `PartialOpaque` support via `Expr(:opaque_closure, ...)` (#55068) This can be useful for when an OpaqueClosure is desired, e.g., as an invalidation barrier or when you know it passes through an inference barrier naturally (in my case, a mutable type). This is intentionally left undocumented for now. --- base/opaque_closure.jl | 4 ++-- src/julia-syntax.scm | 10 ++++++---- test/precompile.jl | 7 +++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index 779cbf55ceaf3..0f1fdf47afed8 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -18,7 +18,7 @@ the argument type may be fixed length even if the function is variadic. This interface is experimental and subject to change or removal without notice. """ macro opaque(ex) - esc(Expr(:opaque_closure, nothing, nothing, nothing, ex)) + esc(Expr(:opaque_closure, nothing, nothing, nothing, #= allow_partial =# true, ex)) end macro opaque(ty, ex) @@ -34,7 +34,7 @@ macro opaque(ty, ex) end AT = (AT !== :_) ? AT : nothing RT = (RT !== :_) ? RT : nothing - return esc(Expr(:opaque_closure, AT, RT, RT, ex)) + return esc(Expr(:opaque_closure, AT, RT, RT, #= allow_partial =# true, ex)) end # OpaqueClosure construction from pre-inferred CodeInfo/IRCode diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 5636caa48e6e6..a2d3ffdd66f67 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2435,7 +2435,8 @@ (let* ((argt (something (list (expand-forms (cadr e)) #f))) (rt_lb (something (list (expand-forms (caddr e)) #f))) (rt_ub (something (list (expand-forms (cadddr e)) #f))) - (F (caddddr e)) + (allow-partial (caddddr e)) + (F (cadddddr e)) (isva (let* ((arglist (function-arglist F)) (lastarg (and (pair? arglist) (last arglist)))) (if (and argt (any (lambda (arg) @@ -2460,7 +2461,7 @@ (let* ((argtype (foldl (lambda (var ex) `(call (core UnionAll) ,var ,ex)) (expand-forms `(curly (core Tuple) ,@argtypes)) (reverse tvars)))) - `(_opaque_closure ,(or argt argtype) ,rt_lb ,rt_ub ,isva ,(length argtypes) ,functionloc ,lam)))) + `(_opaque_closure ,(or argt argtype) ,rt_lb ,rt_ub ,isva ,(length argtypes) ,allow-partial ,functionloc ,lam)))) 'block (lambda (e) @@ -4028,7 +4029,8 @@ f(x) = yt(x) ((_opaque_closure) (let* ((isva (car (cddddr e))) (nargs (cadr (cddddr e))) - (functionloc (caddr (cddddr e))) + (allow-partial (caddr (cddddr e))) + (functionloc (cadddr (cddddr e))) (lam2 (last e)) (vis (lam:vinfo lam2)) (cvs (map car (cadr vis)))) @@ -4040,7 +4042,7 @@ f(x) = yt(x) v))) cvs))) `(new_opaque_closure - ,(cadr e) ,(or (caddr e) '(call (core apply_type) (core Union))) ,(or (cadddr e) '(core Any)) (true) + ,(cadr e) ,(or (caddr e) '(call (core apply_type) (core Union))) ,(or (cadddr e) '(core Any)) ,allow-partial (opaque_closure_method (null) ,nargs ,isva ,functionloc ,(convert-lambda lam2 (car (lam:args lam2)) #f '() (symbol-to-idx-map cvs))) ,@var-exprs)))) ((method) diff --git a/test/precompile.jl b/test/precompile.jl index f45eb4bb1e79e..3e8fe44e1b2f0 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -2012,6 +2012,13 @@ precompile_test_harness("Generated Opaque") do load_path Expr(:opaque_closure_method, nothing, 0, false, lno, ci)) end @assert oc_re_generated_no_partial()() === 1 + @generated function oc_re_generated_no_partial_macro() + AT = nothing + RT = nothing + allow_partial = false # makes this legal to generate during pre-compile + return Expr(:opaque_closure, AT, RT, RT, allow_partial, :(()->const_int_barrier())) + end + @assert oc_re_generated_no_partial_macro()() === 1 end """) Base.compilecache(Base.PkgId("GeneratedOpaque")) From 19ac316951d0aa3ffdddbb84cde62648a109259c Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 29 Aug 2024 20:00:25 +0530 Subject: [PATCH 52/94] Indexing in diag for structured matrices (#55610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This would support a wider range of types, and won't be limited to ones that support `zero(T)`. The following would work after this: ```julia julia> D = Diagonal(fill([1 2; 3 4], 4)) 4×4 Diagonal{Matrix{Int64}, Vector{Matrix{Int64}}}: [1 2; 3 4] ⋅ ⋅ ⋅ ⋅ [1 2; 3 4] ⋅ ⋅ ⋅ ⋅ [1 2; 3 4] ⋅ ⋅ ⋅ ⋅ [1 2; 3 4] julia> diag(D,2) 2-element Vector{Matrix{Int64}}: [0 0; 0 0] [0 0; 0 0] ``` Performance in the common case seems unaffected (the variation is probably noise): ```julia julia> D = Diagonal(rand(1000)); julia> @btime diag($D, 2); 543.836 ns (3 allocations: 7.88 KiB) # nightly 429.551 ns (3 allocations: 7.88 KiB) # This PR ``` --------- Co-authored-by: Daniel Karrasch --- stdlib/LinearAlgebra/src/bidiag.jl | 6 +++++- stdlib/LinearAlgebra/src/diagonal.jl | 10 +++++++--- stdlib/LinearAlgebra/src/tridiag.jl | 12 ++++++++++-- stdlib/LinearAlgebra/test/bidiag.jl | 3 +++ stdlib/LinearAlgebra/test/diagonal.jl | 3 +++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index a997347eabd58..d86bad7e41435 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -409,7 +409,11 @@ function diag(M::Bidiagonal{T}, n::Integer=0) where T elseif (n == 1 && M.uplo == 'U') || (n == -1 && M.uplo == 'L') return copyto!(similar(M.ev, length(M.ev)), M.ev) elseif -size(M,1) <= n <= size(M,1) - return fill!(similar(M.dv, size(M,1)-abs(n)), zero(T)) + v = similar(M.dv, size(M,1)-abs(n)) + for i in eachindex(v) + v[i] = M[BandIndex(n,i)] + end + return v else throw(ArgumentError(LazyString(lazy"requested diagonal, $n, must be at least $(-size(M, 1)) ", lazy"and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 92f399bb774ff..526ec20ddafa1 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -753,10 +753,14 @@ function diag(D::Diagonal{T}, k::Integer=0) where T if k == 0 return copyto!(similar(D.diag, length(D.diag)), D.diag) elseif -size(D,1) <= k <= size(D,1) - return fill!(similar(D.diag, size(D,1)-abs(k)), zero(T)) + v = similar(D.diag, size(D,1)-abs(k)) + for i in eachindex(v) + v[i] = D[BandIndex(k, i)] + end + return v else - throw(ArgumentError(string("requested diagonal, $k, must be at least $(-size(D, 1)) ", - "and at most $(size(D, 2)) for an $(size(D, 1))-by-$(size(D, 2)) matrix"))) + throw(ArgumentError(LazyString(lazy"requested diagonal, $k, must be at least $(-size(D, 1)) ", + lazy"and at most $(size(D, 2)) for an $(size(D, 1))-by-$(size(D, 2)) matrix"))) end end tr(D::Diagonal) = sum(tr, D.diag) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index c2806c21c00ff..3f8eb5da9fc9d 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -198,7 +198,11 @@ function diag(M::SymTridiagonal{T}, n::Integer=0) where T<:Number elseif absn == 1 return copyto!(similar(M.ev, length(M.dv)-1), _evview(M)) elseif absn <= size(M,1) - return fill!(similar(M.dv, size(M,1)-absn), zero(T)) + v = similar(M.dv, size(M,1)-absn) + for i in eachindex(v) + v[i] = M[BandIndex(n,i)] + end + return v else throw_diag_outofboundserror(n, size(M)) end @@ -660,7 +664,11 @@ function diag(M::Tridiagonal{T}, n::Integer=0) where T elseif n == 1 return copyto!(similar(M.du, length(M.du)), M.du) elseif abs(n) <= size(M,1) - return fill!(similar(M.d, size(M,1)-abs(n)), zero(T)) + v = similar(M.d, size(M,1)-abs(n)) + for i in eachindex(v) + v[i] = M[BandIndex(n,i)] + end + return v else throw(ArgumentError(LazyString(lazy"requested diagonal, $n, must be at least $(-size(M, 1)) ", lazy"and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index 387657ba12d04..ef50658a642fb 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -829,6 +829,9 @@ end end end + @test diag(BU, -1) == [zeros(size(dv[i+1], 1), size(dv[i],2)) for i in 1:length(dv)-1] + @test diag(BL, 1) == [zeros(size(dv[i], 1), size(dv[i+1],2)) for i in 1:length(dv)-1] + M = ones(2,2) for n in 0:1 dv = fill(M, n) diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index 866c11b9931cd..83d5e4fcdf170 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -781,6 +781,9 @@ end @test transpose(Dherm) == Diagonal([[1 1-im; 1+im 1], [1 1-im; 1+im 1]]) @test adjoint(Dsym) == Diagonal([[1 1-im; 1-im 1], [1 1-im; 1-im 1]]) @test transpose(Dsym) == Dsym + @test diag(D, 0) == diag(D) == [[1 2; 3 4], [1 2; 3 4]] + @test diag(D, 1) == diag(D, -1) == [zeros(Int,2,2)] + @test diag(D, 2) == diag(D, -2) == [] v = [[1, 2], [3, 4]] @test Dherm' * v == Dherm * v From b5af119a6c608de43d6591a6c4129e9369239898 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 29 Aug 2024 20:43:47 +0530 Subject: [PATCH 53/94] Fix algebra for block SymTridiagonal matrices (#55383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, algebra between a `SymTridiagonal` and another structured matrix uses the bands of the `SymTridiagonal` directly to compute the result. However, this may lead to incorrect results for block matrices, where the elements are symmetrized or transposed. This PR resolves such issues by introducing some internal functions that apply the transforms before the addition/subtraction or equality check is carried out. Fixes issues like ```julia julia> using LinearAlgebra, StaticArrays julia> m = SMatrix{2,2}(1:4); julia> S = SymTridiagonal(fill(m,4), fill(m,3)) 4×4 SymTridiagonal{SMatrix{2, 2, Int64, 4}, Vector{SMatrix{2, 2, Int64, 4}}}: [1 3; 3 4] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [1 3; 3 4] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [1 3; 3 4] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [1 3; 3 4] julia> D = Diagonal(fill(m,4)) 4×4 Diagonal{SMatrix{2, 2, Int64, 4}, Vector{SMatrix{2, 2, Int64, 4}}}: [1 3; 2 4] ⋅ ⋅ ⋅ ⋅ [1 3; 2 4] ⋅ ⋅ ⋅ ⋅ [1 3; 2 4] ⋅ ⋅ ⋅ ⋅ [1 3; 2 4] julia> S + D 4×4 SymTridiagonal{SMatrix{2, 2, Int64, 4}, Vector{SMatrix{2, 2, Int64, 4}}}: [2 6; 6 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 6 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 6 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 6 8] julia> S[1] + D[1] 2×2 SMatrix{2, 2, Int64, 4} with indices SOneTo(2)×SOneTo(2): 2 6 5 8 julia> S[1] + D[1] == (S + D)[1] false ``` With this PR, ```julia julia> S + D 4×4 Tridiagonal{SMatrix{2, 2, Int64, 4}, Vector{SMatrix{2, 2, Int64, 4}}}: [2 6; 5 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 5 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 5 8] [1 3; 2 4] ⋅ ⋅ [1 2; 3 4] [2 6; 5 8] julia> S[1] + D[1] == (S + D)[1] true ``` This would also make https://github.com/JuliaLang/julia/pull/50423 simpler. --- stdlib/LinearAlgebra/src/special.jl | 77 +++++++++++++++++++--------- stdlib/LinearAlgebra/test/special.jl | 60 ++++++++++++++++++++++ test/testhelpers/SizedArrays.jl | 8 ++- 3 files changed, 120 insertions(+), 25 deletions(-) diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 28a1b4b1b2eab..5fea1e32460ff 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -21,16 +21,19 @@ function Tridiagonal(A::Bidiagonal) Tridiagonal(A.uplo == 'U' ? z : A.ev, A.dv, A.uplo == 'U' ? A.ev : z) end +_diagview(S::SymTridiagonal{<:Number}) = S.dv +_diagview(S::SymTridiagonal) = view(S, diagind(S, IndexStyle(S))) + # conversions from SymTridiagonal to other special matrix types -Diagonal(A::SymTridiagonal) = Diagonal(A.dv) +Diagonal(A::SymTridiagonal) = Diagonal(_diagview(A)) # These can fail when ev has the same length as dv # TODO: Revisit when a good solution for #42477 is found -Bidiagonal(A::SymTridiagonal) = +Bidiagonal(A::SymTridiagonal{<:Number}) = iszero(A.ev) ? Bidiagonal(A.dv, A.ev, :U) : throw(ArgumentError("matrix cannot be represented as Bidiagonal")) -Tridiagonal(A::SymTridiagonal) = - Tridiagonal(copy(A.ev), A.dv, A.ev) +Tridiagonal(A::SymTridiagonal{<:Number}) = + Tridiagonal(A.ev, A.dv, A.ev) # conversions from Tridiagonal to other special matrix types Diagonal(A::Tridiagonal) = Diagonal(A.d) @@ -163,26 +166,45 @@ function (-)(A::Diagonal, B::Bidiagonal) Bidiagonal(newdv, typeof(newdv)(-B.ev), B.uplo) end +# Return a SymTridiagonal if the elements of `newdv` are +# statically known to be symmetric. Return a Tridiagonal otherwise +function _symtri_or_tri(dl, d, du) + new_du = oftype(d, du) + new_dl = oftype(d, dl) + if symmetric_type(eltype(d)) == eltype(d) + SymTridiagonal(d, new_du) + else + Tridiagonal(new_dl, d, new_du) + end +end + @commutative function (+)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag + B.dv - SymTridiagonal(A.diag + B.dv, typeof(newdv)(B.ev)) + newdv = A.diag + _diagview(B) + _symtri_or_tri(_evview_transposed(B), newdv, _evview(B)) end function (-)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag - B.dv - SymTridiagonal(newdv, typeof(newdv)(-B.ev)) + newdv = A.diag - _diagview(B) + _symtri_or_tri(-_evview_transposed(B), newdv, -_evview(B)) end function (-)(A::SymTridiagonal, B::Diagonal) - newdv = A.dv - B.diag - SymTridiagonal(newdv, typeof(newdv)(A.ev)) + newdv = _diagview(A) - B.diag + _symtri_or_tri(_evview_transposed(A), newdv, _evview(A)) end # this set doesn't have the aforementioned problem - -@commutative (+)(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl+_evview(B), A.d+B.dv, A.du+_evview(B)) --(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl-_evview(B), A.d-B.dv, A.du-_evview(B)) --(A::SymTridiagonal, B::Tridiagonal) = Tridiagonal(_evview(A)-B.dl, A.dv-B.d, _evview(A)-B.du) +_evview_transposed(S::SymTridiagonal{<:Number}) = _evview(S) +_evview_transposed(S::SymTridiagonal) = transpose.(_evview(S)) +@commutative function (+)(A::Tridiagonal, B::SymTridiagonal) + Tridiagonal(A.dl+_evview_transposed(B), A.d+_diagview(B), A.du+_evview(B)) +end +function -(A::Tridiagonal, B::SymTridiagonal) + Tridiagonal(A.dl-_evview_transposed(B), A.d-_diagview(B), A.du-_evview(B)) +end +function -(A::SymTridiagonal, B::Tridiagonal) + Tridiagonal(_evview_transposed(A)-B.dl, _diagview(A)-B.d, _evview(A)-B.du) +end @commutative function (+)(A::Diagonal, B::Tridiagonal) newdv = A.diag + B.d @@ -215,18 +237,18 @@ function (-)(A::Tridiagonal, B::Bidiagonal) end @commutative function (+)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv + B.dv - Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(_evview(B)), A.dv+B.dv, A.ev+_evview(B)) : (A.ev+_evview(B), A.dv+B.dv, typeof(newdv)(_evview(B))))...) + newdv = A.dv + _diagview(B) + Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(_evview_transposed(B)), newdv, A.ev+_evview(B)) : (A.ev+_evview_transposed(B), newdv, typeof(newdv)(_evview(B))))...) end function (-)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv - B.dv - Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-_evview(B)), newdv, A.ev-_evview(B)) : (A.ev-_evview(B), newdv, typeof(newdv)(-_evview(B))))...) + newdv = A.dv - _diagview(B) + Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-_evview_transposed(B)), newdv, A.ev-_evview(B)) : (A.ev-_evview_transposed(B), newdv, typeof(newdv)(-_evview(B))))...) end function (-)(A::SymTridiagonal, B::Bidiagonal) - newdv = A.dv - B.dv - Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)-B.ev) : (_evview(A)-B.ev, newdv, typeof(newdv)(_evview(A))))...) + newdv = _diagview(A) - B.dv + Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview_transposed(A)), newdv, _evview(A)-B.ev) : (_evview_transposed(A)-B.ev, newdv, typeof(newdv)(_evview(A))))...) end @commutative function (+)(A::Tridiagonal, B::UniformScaling) @@ -256,7 +278,7 @@ function (-)(A::UniformScaling, B::Tridiagonal) end function (-)(A::UniformScaling, B::SymTridiagonal) dv = Ref(A) .- B.dv - SymTridiagonal(dv, convert(typeof(dv), -B.ev)) + SymTridiagonal(dv, convert(typeof(dv), -_evview(B))) end function (-)(A::UniformScaling, B::Bidiagonal) dv = Ref(A) .- B.dv @@ -286,7 +308,14 @@ _small_enough(A::Union{Diagonal, Bidiagonal}) = size(A, 1) <= 1 _small_enough(A::Tridiagonal) = size(A, 1) <= 2 _small_enough(A::SymTridiagonal) = size(A, 1) <= 2 -function fill!(A::Union{Diagonal,Bidiagonal,Tridiagonal,SymTridiagonal}, x) +function fill!(A::Union{Diagonal,Bidiagonal,Tridiagonal}, x) + xT = convert(eltype(A), x) + (iszero(xT) || _small_enough(A)) && return fillstored!(A, xT) + throw(ArgumentError(lazy"array of type $(typeof(A)) and size $(size(A)) can + not be filled with $x, since some of its entries are constrained.")) +end +function fill!(A::SymTridiagonal, x) + issymmetric(x) || throw(ArgumentError("cannot fill a SymTridiagonal with an asymmetric value")) xT = convert(eltype(A), x) (iszero(xT) || _small_enough(A)) && return fillstored!(A, xT) throw(ArgumentError(lazy"array of type $(typeof(A)) and size $(size(A)) can @@ -399,7 +428,7 @@ end # SymTridiagonal == Tridiagonal is already defined in tridiag.jl ==(A::Diagonal, B::Bidiagonal) = iszero(B.ev) && A.diag == B.dv -==(A::Diagonal, B::SymTridiagonal) = iszero(_evview(B)) && A.diag == B.dv +==(A::Diagonal, B::SymTridiagonal) = iszero(_evview(B)) && A.diag == _diagview(B) ==(B::Bidiagonal, A::Diagonal) = A == B ==(A::Diagonal, B::Tridiagonal) = iszero(B.dl) && iszero(B.du) && A.diag == B.d ==(B::Tridiagonal, A::Diagonal) = A == B @@ -413,7 +442,7 @@ function ==(A::Bidiagonal, B::Tridiagonal) end ==(B::Tridiagonal, A::Bidiagonal) = A == B -==(A::Bidiagonal, B::SymTridiagonal) = iszero(_evview(B)) && iszero(A.ev) && A.dv == B.dv +==(A::Bidiagonal, B::SymTridiagonal) = iszero(_evview(B)) && iszero(A.ev) && A.dv == _diagview(B) ==(B::SymTridiagonal, A::Bidiagonal) = A == B # TODO: remove these deprecations (used by SparseArrays in the past) diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 9bb84ba0e9d03..8d3733e6b1289 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -5,6 +5,10 @@ module TestSpecial using Test, LinearAlgebra, Random using LinearAlgebra: rmul!, BandIndex +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + n= 10 #Size of matrix to test Random.seed!(1) @@ -786,4 +790,60 @@ end end end +@testset "block SymTridiagonal" begin + m = SizedArrays.SizedArray{(2,2)}(reshape([1:4;;],2,2)) + S = SymTridiagonal(fill(m,4), fill(m,3)) + SA = Array(S) + D = Diagonal(fill(m,4)) + DA = Array(D) + BU = Bidiagonal(fill(m,4), fill(m,3), :U) + BUA = Array(BU) + BL = Bidiagonal(fill(m,4), fill(m,3), :L) + BLA = Array(BL) + T = Tridiagonal(fill(m,3), fill(m,4), fill(m,3)) + TA = Array(T) + IA = Array(Diagonal(fill(one(m), 4))) + @test S + D == D + S == SA + DA + @test S - D == -(D - S) == SA - DA + @test S + BU == SA + BUA + @test S - BU == -(BU - S) == SA - BUA + @test S + BL == SA + BLA + @test S - BL == -(BL - S) == SA - BLA + @test S + T == SA + TA + @test S - T == -(T - S) == SA - TA + @test S + S == SA + SA + @test S - S == -(S - S) == SA - SA + @test S + I == I + S == SA + IA + @test S - I == -(I - S) == SA - IA + + @test S == S + @test S != D + @test S != BL + @test S != BU + @test S != T + + @test_throws ArgumentError fill!(S, m) + S_small = SymTridiagonal(fill(m,2), fill(m,1)) + @test_throws "cannot fill a SymTridiagonal with an asymmetric value" fill!(S, m) + fill!(S_small, Symmetric(m)) + @test all(==(Symmetric(m)), S_small) + + @testset "diag" begin + m = SizedArrays.SizedArray{(2,2)}([1 3; 3 4]) + D = Diagonal(fill(m,4)) + z = fill(zero(m),3) + d = fill(m,4) + BU = Bidiagonal(d, z, :U) + BL = Bidiagonal(d, z, :L) + T = Tridiagonal(z, d, z) + for ev in (fill(zero(m),3), fill(zero(m),4)) + SD = SymTridiagonal(fill(m,4), ev) + @test SD == D == SD + @test SD == BU == SD + @test SD == BL == SD + @test SD == T == SD + end + end +end + end # module TestSpecial diff --git a/test/testhelpers/SizedArrays.jl b/test/testhelpers/SizedArrays.jl index a435ca7591cac..495fe03347ee7 100644 --- a/test/testhelpers/SizedArrays.jl +++ b/test/testhelpers/SizedArrays.jl @@ -36,10 +36,16 @@ struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} SZ == size(data) || throw(ArgumentError("size mismatch!")) new{SZ,T,N,A}(A(data)) end + function SizedArray{SZ,T,N}(data::A) where {SZ,T,N,A<:AbstractArray{T,N}} + SizedArray{SZ,T,N,A}(data) + end + function SizedArray{SZ,T}(data::A) where {SZ,T,N,A<:AbstractArray{T,N}} + SizedArray{SZ,T,N,A}(data) + end end SizedMatrix{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,2,A} SizedVector{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,1,A} -Base.convert(::Type{SizedArray{SZ,T,N,A}}, data::AbstractArray) where {SZ,T,N,A} = SizedArray{SZ,T,N,A}(data) +Base.convert(::Type{S}, data::AbstractArray) where {S<:SizedArray} = data isa S ? data : S(data) # Minimal AbstractArray interface Base.size(a::SizedArray) = size(typeof(a)) From 7ea8a9a3a69faae0202ab54c0fde3b111bd48b97 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 29 Aug 2024 13:21:22 -0400 Subject: [PATCH 54/94] Refactor `Binding` data structures in preparation for partition (#54788) This is a re-worked extraction of #54654, adjusted to support the new semantics arrived at over the course of that thread. Note that this is the data-structure change only. The semantics in this PR still match current master to the greatest extent possible. The core idea here is to split `Binding` in two: A new `Binding` with minimal data and a `BindingPartition` that holds all data that is world-age partitioned. In the present PR, these are always in 1:1 correspondednce, but after #54654, there will be multiple `BindingPartition`s for every `Binding`. Essentially the `owner` and `ty` fields have been merged into a new `restriction` field of `BindingPartition`, which may also hold the value of a constant (consistent with the final semantics reached in #54654). The non-partitioned binding->value field is now used exclusively for non-constant globals. The disambiguation for how to interpret the `restriction` field happens via flags. `->imported` grew to 2 bits and can now be one of `NONE`/`IMPLICIT`/ `EXPLICIT`/`GUARD`. `GUARD` corresponds to the old `b->owner == NULL` case. `NONE` corresponds to the old `b->owner == b` case, while IMPLICIT/EXPLICIT correspond to `b->owner != b` and the old `imported` flag. Other than that, the behavior of the flags is unchanged. Additionally, fields are provided for `min_world`/`max_world`/`next`, but these are unused in this PR and prepratory only. --- base/client.jl | 2 +- base/reflection.jl | 21 ++ doc/src/manual/variables-and-scoping.md | 6 +- src/ast.c | 13 +- src/builtins.c | 18 +- src/cgutils.cpp | 17 - src/codegen.cpp | 210 ++++++----- src/dlload.c | 2 +- src/gc-heap-snapshot.cpp | 7 + src/gc-heap-snapshot.h | 9 + src/gc-interface.h | 8 + src/gc-stock.c | 10 + src/gf.c | 56 ++- src/interpreter.c | 4 +- src/jl_exported_data.inc | 1 + src/jl_exported_funcs.inc | 3 +- src/jltypes.c | 22 +- src/julia.h | 92 ++++- src/julia_internal.h | 88 ++++- src/method.c | 45 +-- src/module.c | 479 +++++++++++++++--------- src/rtutils.c | 12 +- src/staticdata.c | 121 ++++-- src/support/dtypes.h | 7 + src/toplevel.c | 196 ++++++---- stdlib/REPL/src/REPL.jl | 40 +- stdlib/REPL/src/precompile.jl | 1 + test/core.jl | 2 +- test/precompile.jl | 2 +- test/syntax.jl | 79 +++- 30 files changed, 1031 insertions(+), 542 deletions(-) diff --git a/base/client.jl b/base/client.jl index 3aebfaf6de696..0290d27b09cf0 100644 --- a/base/client.jl +++ b/base/client.jl @@ -417,7 +417,7 @@ function load_REPL() return nothing end -global active_repl +global active_repl::Any global active_repl_backend = nothing function run_fallback_repl(interactive::Bool) diff --git a/base/reflection.jl b/base/reflection.jl index 4b491ca9f6bd4..2ddd34b0f73c1 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -207,6 +207,27 @@ function _fieldnames(@nospecialize t) return t.name.names end +const BINDING_KIND_GLOBAL = 0x0 +const BINDING_KIND_CONST = 0x1 +const BINDING_KIND_CONST_IMPORT = 0x2 +const BINDING_KIND_IMPLICIT = 0x3 +const BINDING_KIND_EXPLICIT = 0x4 +const BINDING_KIND_IMPORTED = 0x5 +const BINDING_KIND_FAILED = 0x6 +const BINDING_KIND_DECLARED = 0x7 +const BINDING_KIND_GUARD = 0x8 + +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) + ccall(:jl_get_globalref_partition, Ref{Core.BindingPartition}, (Any, UInt), gr, world) +end + +binding_kind(bpart::Core.BindingPartition) = ccall(:jl_bpart_get_kind, UInt8, (Any,), bpart) +binding_kind(m::Module, s::Symbol) = binding_kind(lookup_binding_partition(tls_world_age(), GlobalRef(m, s))) + """ fieldname(x::DataType, i::Integer) diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index de97ff296e37e..64a12ea88c7dd 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -743,7 +743,7 @@ ERROR: invalid redefinition of constant x julia> const y = 1.0 1.0 -julia> y = 2.0 +julia> const y = 2.0 WARNING: redefinition of constant y. This may fail, cause incorrect answers, or produce other errors. 2.0 ``` @@ -761,7 +761,7 @@ julia> const a = [1] 1-element Vector{Int64}: 1 -julia> a = [1] +julia> const a = [1] WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors. 1-element Vector{Int64}: 1 @@ -782,7 +782,7 @@ f (generic function with 1 method) julia> f() 1 -julia> x = 2 +julia> const x = 2 WARNING: redefinition of constant x. This may fail, cause incorrect answers, or produce other errors. 2 diff --git a/src/ast.c b/src/ast.c index 7c775bf25d486..26b95225fbf1c 100644 --- a/src/ast.c +++ b/src/ast.c @@ -175,7 +175,8 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]); jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); - return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + return (bpart != NULL && decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GLOBAL) ? fl_ctx->T : fl_ctx->F; } static value_t fl_nothrow_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) @@ -204,8 +205,14 @@ static value_t fl_nothrow_julia_global(fl_context_t *fl_ctx, value_t *args, uint var = scmsym_to_julia(fl_ctx, args[1]); } jl_binding_t *b = jl_get_module_binding(mod, var, 0); - b = b ? jl_atomic_load_relaxed(&b->owner) : NULL; - return b != NULL && jl_atomic_load_relaxed(&b->value) != NULL ? fl_ctx->T : fl_ctx->F; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (!bpart) + return fl_ctx->F; + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + return fl_ctx->F; + return (jl_bkind_is_some_constant(decode_restriction_kind(pku)) ? + decode_restriction_value(pku) : jl_atomic_load_relaxed(&b->value)) != NULL ? fl_ctx->T : fl_ctx->F; } static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT diff --git a/src/builtins.c b/src/builtins.c index 045a9914f5078..8019ee3c0e2c6 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1369,19 +1369,10 @@ JL_CALLABLE(jl_f_get_binding_type) jl_sym_t *var = (jl_sym_t*)args[1]; JL_TYPECHK(get_binding_type, module, (jl_value_t*)mod); JL_TYPECHK(get_binding_type, symbol, (jl_value_t*)var); - jl_value_t *ty = jl_get_binding_type(mod, var); - if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_module_binding(mod, var, 0); - if (b == NULL) - return (jl_value_t*)jl_any_type; - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b) - return (jl_value_t*)jl_any_type; - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - return jl_atomic_load_relaxed(&b->ty); - } - return ty; + jl_value_t *ret = jl_get_binding_type(mod, var); + if (ret == jl_nothing) + return (jl_value_t*)jl_any_type; + return ret; } JL_CALLABLE(jl_f_swapglobal) @@ -2509,6 +2500,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("QuoteNode", (jl_value_t*)jl_quotenode_type); add_builtin("NewvarNode", (jl_value_t*)jl_newvarnode_type); add_builtin("Binding", (jl_value_t*)jl_binding_type); + add_builtin("BindingPartition", (jl_value_t*)jl_binding_partition_type); add_builtin("GlobalRef", (jl_value_t*)jl_globalref_type); add_builtin("NamedTuple", (jl_value_t*)jl_namedtuple_type); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index f911ef17eea38..2d2d2aed22069 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -568,23 +568,6 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p) return load; } -// Returns ctx.types().T_pjlvalue -static Value *literal_pointer_val(jl_codectx_t &ctx, jl_binding_t *p) -{ - // emit a pointer to any jl_value_t which will be valid across reloading code - if (p == NULL) - return Constant::getNullValue(ctx.types().T_pjlvalue); - // bindings are prefixed with jl_bnd# - jl_globalref_t *gr = p->globalref; - Value *pgv = gr ? julia_pgv(ctx, "jl_bnd#", gr->name, gr->mod, p) : julia_pgv(ctx, "jl_bnd#", p); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - auto load = ai.decorateInst(maybe_mark_load_dereferenceable( - ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), - false, sizeof(jl_binding_t), alignof(jl_binding_t))); - setName(ctx.emission_context, load, pgv->getName()); - return load; -} - // bitcast a value, but preserve its address space when dealing with pointer types static Value *emit_bitcast(jl_codectx_t &ctx, Value *v, Type *jl_value) { diff --git a/src/codegen.cpp b/src/codegen.cpp index e499d1193dee6..4091ec6c03db0 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -907,14 +907,6 @@ static const auto jlcheckassignonce_func = new JuliaFunction<>{ {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, nullptr, }; -static const auto jldeclareconst_func = new JuliaFunction<>{ - XSTR(jl_declare_constant), - [](LLVMContext &C) { - auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); - return FunctionType::get(getVoidTy(C), - {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, - nullptr, -}; static const auto jldeclareconstval_func = new JuliaFunction<>{ XSTR(jl_declare_constant_val), [](LLVMContext &C) { @@ -951,6 +943,16 @@ static const auto jlgetbindingwrorerror_func = new JuliaFunction<>{ }, nullptr, }; +static const auto jlgetbindingvalue_func = new JuliaFunction<>{ + XSTR(jl_reresolve_binding_value_seqcst), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_pjlvalue}, false); + }, + nullptr, +}; static const auto jlboundp_func = new JuliaFunction<>{ XSTR(jl_boundp), [](LLVMContext &C) { @@ -1010,13 +1012,12 @@ static const auto jlmethod_func = new JuliaFunction<>{ nullptr, }; static const auto jlgenericfunction_func = new JuliaFunction<>{ - XSTR(jl_generic_function_def), + XSTR(jl_declare_const_gf), [](LLVMContext &C) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); auto T_pjlvalue = PointerType::get(T_jlvalue, 0); auto T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); - auto T_pprjlvalue = PointerType::get(T_prjlvalue, 0); - return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue}, false); + return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; @@ -2930,10 +2931,11 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (jl_is_globalref(ex)) { s = jl_globalref_name(ex); jl_binding_t *b = jl_get_binding(jl_globalref_mod(ex), s); - if (b && b->constp) { + jl_value_t *v = jl_get_binding_value_if_const(b); + if (v) { if (b->deprecated) cg_bdw(ctx, s, b); - return jl_atomic_load_relaxed(&b->value); + return v; } return NULL; } @@ -2952,10 +2954,11 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) 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); - if (b && b->constp) { + jl_value_t *v = jl_get_binding_value_if_const(b); + if (v) { if (b->deprecated) cg_bdw(ctx, s, b); - return jl_atomic_load_relaxed(&b->value); + return v; } } } @@ -3192,18 +3195,53 @@ static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val) static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order) { - jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false, false); - if (bp == NULL) - return jl_cgval_t(); - bp = julia_binding_pvalue(ctx, bp); - jl_value_t *ty = nullptr; - if (bnd) { - jl_value_t *v = jl_atomic_load_acquire(&bnd->value); // acquire value for ty - if (v != NULL && bnd->constp) - return mark_julia_const(ctx, v); - ty = jl_atomic_load_relaxed(&bnd->ty); + jl_binding_t *bnd = jl_get_module_binding(mod, name, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(bnd, ctx.max_world); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + // try to look this up now. + // TODO: This is bad and we'd like to delete it. + jl_get_binding(mod, name); + } + assert(bnd); + Value *bp = NULL; + // bpart was updated in place - this will change with full partition + pku = jl_atomic_load_acquire(&bpart->restriction); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + // Redo the lookup at runtime + bp = julia_binding_gv(ctx, bnd); + Value *v = ctx.builder.CreateCall(prepare_call(jlgetbindingvalue_func), { bp }); + undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name, (jl_value_t*)mod); + return mark_julia_type(ctx, v, true, jl_any_type); + } else { + while (true) { + if (!bpart) + break; + if (!jl_bkind_is_some_import(decode_restriction_kind(pku))) + break; + if (bnd->deprecated) { + cg_bdw(ctx, name, bnd); + } + bnd = (jl_binding_t*)decode_restriction_value(pku); + bpart = jl_get_binding_partition(bnd, ctx.max_world); + pku = jl_atomic_load_acquire(&bpart->restriction); + } + if (bpart && jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + jl_value_t *constval = decode_restriction_value(pku); + if (!constval) { + undef_var_error_ifnot(ctx, ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), name, (jl_value_t*)mod); + return jl_cgval_t(); + } + return mark_julia_const(ctx, constval); + } + } + 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) ty = (jl_value_t*)jl_any_type; return update_julia_type(ctx, emit_checked_var(ctx, bp, name, (jl_value_t*)mod, false, ctx.tbaa().tbaa_binding), ty); @@ -3216,43 +3254,47 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s { jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, alloc); + jl_binding_partition_t *bpart = jl_get_binding_partition(bnd, ctx.max_world); if (bp == NULL) return jl_cgval_t(); - if (bnd && !bnd->constp) { - jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); - if (ty != nullptr) { - const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; - if (!ismodifyglobal) { - // TODO: use typeassert in jl_check_binding_wr too - emit_typecheck(ctx, rval, ty, "typeassert"); - rval = update_julia_type(ctx, rval, ty); - if (rval.typ == jl_bottom_type) - return jl_cgval_t(); - } - bool isboxed = true; - bool maybe_null = jl_atomic_load_relaxed(&bnd->value) == NULL; - return typed_store(ctx, - julia_binding_pvalue(ctx, bp), - rval, cmp, ty, - ctx.tbaa().tbaa_binding, - nullptr, - bp, - isboxed, - Order, - FailOrder, - 0, - nullptr, - issetglobal, - isreplaceglobal, - isswapglobal, - ismodifyglobal, - issetglobalonce, - maybe_null, - modifyop, - fname, - mod, - sym); + if (bpart) { + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (!jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + jl_value_t *ty = decode_restriction_value(pku); + if (ty != nullptr) { + const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; + if (!ismodifyglobal) { + // TODO: use typeassert in jl_check_binding_wr too + emit_typecheck(ctx, rval, ty, "typeassert"); + rval = update_julia_type(ctx, rval, ty); + if (rval.typ == jl_bottom_type) + return jl_cgval_t(); + } + bool isboxed = true; + bool maybe_null = jl_atomic_load_relaxed(&bnd->value) == NULL; + return typed_store(ctx, + julia_binding_pvalue(ctx, bp), + rval, cmp, ty, + ctx.tbaa().tbaa_binding, + nullptr, + bp, + isboxed, + Order, + FailOrder, + 0, + nullptr, + issetglobal, + isreplaceglobal, + isswapglobal, + ismodifyglobal, + issetglobalonce, + maybe_null, + modifyop, + fname, + mod, + sym); + } } } Value *m = literal_pointer_val(ctx, (jl_value_t*)mod); @@ -5437,16 +5479,20 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t jl_binding_t **pbnd, bool assign, bool alloc) { jl_binding_t *b = jl_get_module_binding(m, s, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, ctx.max_world); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); if (assign) { - if (jl_atomic_load_relaxed(&b->owner) == NULL) + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) // not yet declared b = NULL; } else { - b = jl_atomic_load_relaxed(&b->owner); - if (b == NULL) + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { // try to look this up now b = jl_get_binding(m, s); + bpart = jl_get_binding_partition(b, ctx.max_world); + } + pku = jl_walk_binding_inplace(&b, &bpart, ctx.max_world); } if (b == NULL) { // var not found. switch to delayed lookup. @@ -5487,7 +5533,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t return p; } if (assign) { - if (jl_atomic_load_relaxed(&b->owner) != b) { + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_guard(decode_restriction_kind(pku))) { // this will fail at runtime, so defer to the runtime to create the error ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func), { literal_pointer_val(ctx, (jl_value_t*)m), @@ -5606,8 +5652,10 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym, int allow_i name = (jl_sym_t*)sym; } jl_binding_t *bnd = allow_import ? jl_get_binding(modu, name) : jl_get_module_binding(modu, name, 0); - if (bnd && jl_atomic_load_relaxed(&bnd->owner) == bnd) { - if (jl_atomic_load_acquire(&bnd->value) != NULL && bnd->constp) + jl_binding_partition_t *bpart = jl_get_binding_partition(bnd, ctx.min_world); + jl_ptr_kind_union_t pku = bpart ? jl_atomic_load_relaxed(&bpart->restriction) : encode_restriction(NULL, BINDING_KIND_GUARD); + if (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (jl_get_binding_value_if_const(bnd)) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); @@ -6410,13 +6458,11 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ return ghostValue(ctx, jl_nothing_type); } bp = julia_binding_gv(ctx, bnd); - bp = julia_binding_pvalue(ctx, bp); - } - if (bp) { - Value *mdargs[] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, literal_pointer_val(ctx, bnd) }; jl_cgval_t gf = mark_julia_type( ctx, - ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), ArrayRef(mdargs)), + ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), { bp, + literal_pointer_val(ctx, (jl_value_t*)mod), name + }), true, jl_function_type); return gf; @@ -6449,17 +6495,14 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ sym = jl_globalref_name(sym); } if (jl_is_symbol(sym)) { - jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, true); - if (bp) { - if (nargs == 2) { - jl_cgval_t rhs = emit_expr(ctx, args[1]); - ctx.builder.CreateCall(prepare_call(jldeclareconstval_func), - { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, rhs) }); - } else { - ctx.builder.CreateCall(prepare_call(jldeclareconst_func), - { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); - } + jl_binding_t *bnd = jl_get_module_binding(mod, sym, 1); + if (nargs == 2) { + jl_cgval_t rhs = emit_expr(ctx, args[1]); + ctx.builder.CreateCall(prepare_call(jldeclareconstval_func), + { julia_binding_gv(ctx, bnd), literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, rhs) }); + } else { + ctx.builder.CreateCall(prepare_call(jldeclareconstval_func), + { julia_binding_gv(ctx, bnd), literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), ConstantPointerNull::get(cast(ctx.types().T_prjlvalue)) }); } } } @@ -10046,7 +10089,6 @@ static void init_jit_functions(void) add_named_global(memcmp_func, &memcmp); add_named_global(jltypeerror_func, &jl_type_error); add_named_global(jlcheckassign_func, &jl_checked_assignment); - add_named_global(jldeclareconst_func, &jl_declare_constant); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr); add_named_global(jlboundp_func, &jl_boundp); @@ -10060,7 +10102,7 @@ static void init_jit_functions(void) add_named_global(jlcopyast_func, &jl_copy_ast); //add_named_global(jlnsvec_func, &jl_svec); add_named_global(jlmethod_func, &jl_method_def); - add_named_global(jlgenericfunction_func, &jl_generic_function_def); + add_named_global(jlgenericfunction_func, &jl_declare_const_gf); add_named_global(jlenter_func, &jl_enter_handler); add_named_global(jl_current_exception_func, &jl_current_exception); add_named_global(jlleave_noexcept_func, &jl_pop_handler_noexcept); diff --git a/src/dlload.c b/src/dlload.c index 484c36a228886..91980cc4ecbbf 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -309,7 +309,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, */ if (!abspath && !is_atpath && jl_base_module != NULL) { jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH"), 0); - jl_array_t *DL_LOAD_PATH = (jl_array_t*)(b ? jl_atomic_load_relaxed(&b->value) : NULL); + jl_array_t *DL_LOAD_PATH = (jl_array_t*)(b ? jl_get_binding_value(b) : NULL); if (DL_LOAD_PATH != NULL) { size_t j; for (j = 0; j < jl_array_nrows(DL_LOAD_PATH); j++) { diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index 4fcc66495fc45..fcda11dad4f8a 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -561,6 +561,13 @@ void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t * g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "")); } +void _gc_heap_snapshot_record_binding_partition_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT +{ + _record_gc_edge("binding", from, to, + g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "")); +} + + void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT { // valid alloc_type values are 0, 1, 2 diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h index e7fbb36249ec1..dc5b22bb72eb1 100644 --- a/src/gc-heap-snapshot.h +++ b/src/gc-heap-snapshot.h @@ -32,6 +32,8 @@ void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t byt void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT; // Used for objects that are reachable from the finalizer list void _gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t index) JL_NOTSAFEPOINT; +// Used for objects reachable from the binding partition pointer union +void _gc_heap_snapshot_record_binding_partition_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT; extern int gc_heap_snapshot_enabled; extern int prev_sweep_full; @@ -97,6 +99,13 @@ static inline void gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, } } +static inline void gc_heap_snapshot_record_binding_partition_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_binding_partition_edge(from, to); + } +} + static inline void gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT { if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { diff --git a/src/gc-interface.h b/src/gc-interface.h index 201c5f6e1741e..e543b4b5879f1 100644 --- a/src/gc-interface.h +++ b/src/gc-interface.h @@ -235,6 +235,14 @@ STATIC_INLINE void jl_gc_wb_back(const void *ptr) JL_NOTSAFEPOINT; // in different GC generations (i.e. if the first argument points to an old object and the // second argument points to a young object), and if so, call the write barrier slow-path. STATIC_INLINE void jl_gc_wb(const void *parent, const void *ptr) JL_NOTSAFEPOINT; +// Freshly allocated objects are known to be in the young generation until the next safepoint, +// so write barriers can be omitted until the next allocation. This function is a no-op that +// can be used to annotate that a write barrier would be required were it not for this property +// (as opposed to somebody just having forgotten to think about write barriers). +STATIC_INLINE void jl_gc_wb_fresh(const void *parent, const void *ptr) JL_NOTSAFEPOINT {} +// Used to annotate that a write barrier would be required, but may be omitted because `ptr` +// is known to be an old object. +STATIC_INLINE void jl_gc_wb_knownold(const void *parent, const void *ptr) JL_NOTSAFEPOINT {} // Write-barrier function that must be used after copying multiple fields of an object into // another. It should be semantically equivalent to triggering multiple write barriers – one // per field of the object being copied, but may be special-cased for performance reasons. diff --git a/src/gc-stock.c b/src/gc-stock.c index 3ae14f378a2e7..d25f8917f302d 100644 --- a/src/gc-stock.c +++ b/src/gc-stock.c @@ -2307,6 +2307,16 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ if (npointers == 0) return; uintptr_t nptr = (npointers << 2 | (bits & GC_OLD)); + if (vt == jl_binding_partition_type) { + // BindingPartition has a special union of jl_value_t and flag bits + // but is otherwise regular. + jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_valueof(o); + jl_value_t *val = decode_restriction_value( + jl_atomic_load_relaxed(&bpart->restriction)); + if (val) + gc_heap_snapshot_record_binding_partition_edge((jl_value_t*)bpart, val); + gc_try_claim_and_push(mq, val, &nptr); + } assert((layout->nfields > 0 || layout->flags.fielddesc_type == 3) && "opaque types should have been handled specially"); if (layout->flags.fielddesc_type == 0) { diff --git a/src/gf.c b/src/gf.c index 5ae7644c01363..95bab0d0f832e 100644 --- a/src/gf.c +++ b/src/gf.c @@ -700,40 +700,38 @@ int foreach_mtable_in_module( if ((void*)b == jl_nothing) break; jl_sym_t *name = b->globalref->name; - if (jl_atomic_load_relaxed(&b->owner) == b && b->constp) { - jl_value_t *v = jl_atomic_load_relaxed(&b->value); - if (v) { - jl_value_t *uw = jl_unwrap_unionall(v); - if (jl_is_datatype(uw)) { - jl_typename_t *tn = ((jl_datatype_t*)uw)->name; - if (tn->module == m && tn->name == name && tn->wrapper == v) { - // this is the original/primary binding for the type (name/wrapper) - jl_methtable_t *mt = tn->mt; - if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { - assert(mt->module == m); - if (!visit(mt, env)) - return 0; - } - } - } - else if (jl_is_module(v)) { - jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == name) { - // this is the original/primary binding for the submodule - if (!foreach_mtable_in_module(child, visit, env)) - return 0; - } - } - else if (jl_is_mtable(v)) { - jl_methtable_t *mt = (jl_methtable_t*)v; - if (mt->module == m && mt->name == name) { - // this is probably an external method table here, so let's - // assume so as there is no way to precisely distinguish them + jl_value_t *v = jl_get_binding_value_if_const(b); + if (v) { + jl_value_t *uw = jl_unwrap_unionall(v); + if (jl_is_datatype(uw)) { + jl_typename_t *tn = ((jl_datatype_t*)uw)->name; + if (tn->module == m && tn->name == name && tn->wrapper == v) { + // this is the original/primary binding for the type (name/wrapper) + jl_methtable_t *mt = tn->mt; + if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { + assert(mt->module == m); if (!visit(mt, env)) return 0; } } } + else if (jl_is_module(v)) { + jl_module_t *child = (jl_module_t*)v; + if (child != m && child->parent == m && child->name == name) { + // this is the original/primary binding for the submodule + if (!foreach_mtable_in_module(child, visit, env)) + return 0; + } + } + else if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; + if (mt->module == m && mt->name == name) { + // this is probably an external method table here, so let's + // assume so as there is no way to precisely distinguish them + if (!visit(mt, env)) + return 0; + } + } } table = jl_atomic_load_relaxed(&m->bindings); } diff --git a/src/interpreter.c b/src/interpreter.c index 5b96c485aac0d..f9d981687c631 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -94,9 +94,7 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) jl_error("method: invalid declaration"); } jl_binding_t *b = jl_get_binding_for_method_def(modu, fname); - _Atomic(jl_value_t*) *bp = &b->value; - jl_value_t *gf = jl_generic_function_def(fname, modu, bp, b); - return gf; + return jl_declare_const_gf(b, modu, fname); } jl_value_t *atypes = NULL, *meth = NULL, *fname = NULL; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index ff79966b2b01b..8711c14514145 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -51,6 +51,7 @@ XX(jl_floatingpoint_type) \ XX(jl_function_type) \ XX(jl_binding_type) \ + XX(jl_binding_partition_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ XX(jl_enternode_type) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 1976dbe709733..11cc8ee6fddd9 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -97,7 +97,6 @@ XX(jl_cstr_to_string) \ XX(jl_current_exception) \ XX(jl_debug_method_invalidation) \ - XX(jl_declare_constant) \ XX(jl_defines_or_exports_p) \ XX(jl_deprecate_binding) \ XX(jl_dlclose) \ @@ -185,7 +184,7 @@ XX(jl_gc_total_hrtime) \ XX(jl_gdblookup) \ XX(jl_generating_output) \ - XX(jl_generic_function_def) \ + XX(jl_declare_const_gf) \ XX(jl_gensym) \ XX(jl_getaffinity) \ XX(jl_getallocationgranularity) \ diff --git a/src/jltypes.c b/src/jltypes.c index a587552aaa011..adf39162cc7f0 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3148,12 +3148,21 @@ void jl_init_types(void) JL_GC_DISABLED assert(jl_module_type->instance == NULL); jl_compute_field_offsets(jl_module_type); + jl_binding_partition_type = + jl_new_datatype(jl_symbol("BindingPartition"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(5, "restriction", "min_world", "max_world", "next", "reserved"), + jl_svec(5, jl_uint64_type /* Special GC-supported union of Any and flags*/, + jl_ulong_type, jl_ulong_type, jl_any_type/*jl_binding_partition_type*/, jl_ulong_type), + jl_emptysvec, 0, 1, 0); + const static uint32_t binding_partition_atomicfields[] = { 0b01101 }; // Set fields 1, 3, 4 as atomic + jl_binding_partition_type->name->atomicfields = binding_partition_atomicfields; + jl_binding_type = jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "value", "globalref", "owner", "ty", "flags"), - jl_svec(5, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_uint8_type), + jl_perm_symsvec(4, "globalref", "value", "partitions", "flags"), + jl_svec(4, jl_any_type/*jl_globalref_type*/, jl_any_type, jl_binding_partition_type, jl_uint8_type), jl_emptysvec, 0, 1, 0); - const static uint32_t binding_atomicfields[] = { 0x0015 }; // Set fields 1, 3, 4 as atomic + const static uint32_t binding_atomicfields[] = { 0x0005 }; // Set fields 1, 3 as atomic jl_binding_type->name->atomicfields = binding_atomicfields; const static uint32_t binding_constfields[] = { 0x0002 }; // Set fields 2 as constant jl_binding_type->name->constfields = binding_constfields; @@ -3707,8 +3716,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_method_instance_type->types, 4, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 16, jl_voidpointer_type); - jl_svecset(jl_binding_type->types, 1, jl_globalref_type); - jl_svecset(jl_binding_type->types, 2, jl_binding_type); + jl_svecset(jl_binding_type->types, 0, jl_globalref_type); + jl_svecset(jl_binding_partition_type->types, 3, jl_binding_partition_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); @@ -3720,6 +3729,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_unionall_type); jl_compute_field_offsets(jl_simplevector_type); jl_compute_field_offsets(jl_symbol_type); + jl_compute_field_offsets(jl_binding_partition_type); // override ismutationfree for builtin types that are mutable for identity jl_string_type->ismutationfree = jl_string_type->isidentityfree = 1; @@ -3811,7 +3821,7 @@ void post_boot_hooks(void) for (size_t i = 0; i < jl_svec_len(bindings); i++) { if (table[i] != jl_nothing) { jl_binding_t *b = (jl_binding_t*)table[i]; - jl_value_t *v = jl_atomic_load_relaxed(&b->value); + jl_value_t *v = jl_get_binding_value(b); if (v) { if (jl_is_unionall(v)) v = jl_unwrap_unionall(v); diff --git a/src/julia.h b/src/julia.h index 074c50fd0aa21..caa938ffeb0d6 100644 --- a/src/julia.h +++ b/src/julia.h @@ -611,19 +611,84 @@ typedef struct _jl_weakref_t { jl_value_t *value; } jl_weakref_t; +enum jl_partition_kind { + // Constant: This binding partition is a constant declared using `const` + // ->restriction holds the constant value + BINDING_KIND_CONST = 0x0, + // Import Constant: This binding partition is a constant declared using `import A` + // ->restriction holds the constant value + BINDING_KIND_CONST_IMPORT = 0x1, + // Global: This binding partition is a global variable. + // -> restriction holds the type restriction + BINDING_KIND_GLOBAL = 0x2, + // Implicit: The binding was implicitly imported from a `using`'d module. + // ->restriction holds the imported binding + BINDING_KIND_IMPLICIT = 0x3, + // Explicit: The binding was explicitly `using`'d by name + // ->restriction holds the imported binding + BINDING_KIND_EXPLICIT = 0x4, + // Imported: The binding was explicitly `import`'d by name + // ->restriction holds the imported binding + BINDING_KIND_IMPORTED = 0x5, + // Failed: We attempted to import the binding, but the import was ambiguous + // ->restriction is NULL. + BINDING_KIND_FAILED = 0x6, + // Declared: The binding was declared using `global` or similar + // ->restriction is NULL. + BINDING_KIND_DECLARED = 0x7, + // Guard: The binding was looked at, but no global or import was resolved at the time + // ->restriction is NULL. + BINDING_KIND_GUARD = 0x8 +}; + +#ifdef _P64 +// Union of a ptr and a 3 bit field. +typedef uintptr_t jl_ptr_kind_union_t; +#else +typedef struct __attribute__((aligned(8))) { jl_value_t *val; size_t kind; } jl_ptr_kind_union_t; +#endif +typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { + JL_DATA_TYPE + /* union { + * // For ->kind == BINDING_KIND_GLOBAL + * jl_value_t *type_restriction; + * // For ->kind == BINDING_KIND_CONST(_IMPORT) + * jl_value_t *constval; + * // For ->kind in (BINDING_KIND_IMPLICIT, BINDING_KIND_EXPLICIT, BINDING_KIND_IMPORT) + * jl_binding_t *imported; + * } restriction; + * + * Currently: Low 3 bits hold ->kind on _P64 to avoid needing >8 byte atomics + * + * This field is updated atomically with both kind and restriction. The following + * transitions are allowed and modeled by the system: + * + * GUARD -> any + * (DECLARED, FAILED) -> any non-GUARD + * IMPLICIT -> {EXPLICIT, IMPORTED} (->restriction unchanged only) + * + * In addition, we permit (with warning about undefined behavior) changing the restriction + * pointer for CONST(_IMPORT). + * + * All other kind or restriction transitions are disallowed. + */ + _Atomic(jl_ptr_kind_union_t) restriction; + size_t min_world; + _Atomic(size_t) max_world; + _Atomic(struct _jl_binding_partition_t*) next; + size_t reserved; // Reserved for ->kind. Currently this holds the low bits of ->restriction during serialization +} jl_binding_partition_t; + typedef struct _jl_binding_t { JL_DATA_TYPE - _Atomic(jl_value_t*) value; jl_globalref_t *globalref; // cached GlobalRef for this binding - _Atomic(struct _jl_binding_t*) owner; // for individual imported bindings (NULL until 'resolved') - _Atomic(jl_value_t*) ty; // binding type - uint8_t constp:1; + _Atomic(jl_value_t*) value; + _Atomic(jl_binding_partition_t*) partitions; + uint8_t declared:1; uint8_t exportp:1; // `public foo` sets `publicp`, `export foo` sets both `publicp` and `exportp` uint8_t publicp:1; // exportp without publicp is not allowed. - uint8_t imported:1; - uint8_t usingfailed:1; uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package - uint8_t padding:1; + uint8_t padding:3; } jl_binding_t; typedef struct { @@ -915,6 +980,7 @@ extern JL_DLLIMPORT jl_value_t *jl_memoryref_uint8_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_memoryref_any_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_expr_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_binding_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_binding_partition_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_globalref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_linenumbernode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotonode_type JL_GLOBALLY_ROOTED; @@ -1462,6 +1528,7 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT #define jl_is_slotnumber(v) jl_typetagis(v,jl_slotnumber_type) #define jl_is_expr(v) jl_typetagis(v,jl_expr_type) #define jl_is_binding(v) jl_typetagis(v,jl_binding_type) +#define jl_is_binding_partition(v) jl_typetagis(v,jl_binding_partition_type) #define jl_is_globalref(v) jl_typetagis(v,jl_globalref_type) #define jl_is_gotonode(v) jl_typetagis(v,jl_gotonode_type) #define jl_is_gotoifnot(v) jl_typetagis(v,jl_gotoifnot_type) @@ -1760,10 +1827,9 @@ JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, size_t len) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_sym_t *jl_gensym(void); JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, size_t len); JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void); -JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, - jl_module_t *module, - _Atomic(jl_value_t*) *bp, - jl_binding_t *bnd); +JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, jl_sym_t *name); JL_DLLEXPORT jl_method_t *jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, jl_module_t *module); JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, size_t world, jl_code_instance_t **cache); JL_DLLEXPORT jl_code_info_t *jl_copy_code_info(jl_code_info_t *src); @@ -1924,8 +1990,8 @@ JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_s JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs); JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs); JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); -JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var); -JL_DLLEXPORT void jl_declare_constant_val(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val); +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(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) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(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) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); diff --git a/src/julia_internal.h b/src/julia_internal.h index 8ea1940224e66..652aae54860b5 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -805,7 +805,7 @@ JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t *val); -void jl_binding_set_type(jl_binding_t *b, jl_value_t *ty, int error); +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_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); @@ -860,6 +860,92 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva, int isinferred); JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); +EXTERN_INLINE_DECLARE enum jl_partition_kind decode_restriction_kind(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT +{ +#ifdef _P64 + uint8_t bits = (pku & 0x7); + jl_value_t *val = (jl_value_t*)(pku & ~0x7); + + if (val == NULL && bits == BINDING_KIND_IMPLICIT) { + return BINDING_KIND_GUARD; + } + + return (enum jl_partition_kind)bits; +#else + return (enum jl_partition_kind)pku.kind; +#endif +} + +STATIC_INLINE jl_value_t *decode_restriction_value(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT +{ +#ifdef _P64 + jl_value_t *val = (jl_value_t*)(pku & ~0x7); + // This is a little bit of a lie at the moment - it is one of the things that + // can go wrong with binding replacement. + JL_GC_PROMISE_ROOTED(val); + return val; +#else + return pku.val; +#endif +} + +STATIC_INLINE jl_ptr_kind_union_t encode_restriction(jl_value_t *val, enum jl_partition_kind kind) JL_NOTSAFEPOINT +{ +#ifdef _P64 + if (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED) + assert(val == NULL); + if (kind == BINDING_KIND_GUARD) + kind = BINDING_KIND_IMPLICIT; + assert((((uintptr_t)val) & 0x7) == 0); + return ((jl_ptr_kind_union_t)val) | kind; +#else + jl_ptr_kind_union_t ret = { val, kind }; + return ret; +#endif +} + +STATIC_INLINE int jl_bkind_is_some_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { + return kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED; +} + +STATIC_INLINE int jl_bkind_is_some_constant(enum jl_partition_kind kind) JL_NOTSAFEPOINT { + return kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT; +} + +STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT { + return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED; +} + +EXTERN_INLINE_DECLARE jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) JL_NOTSAFEPOINT { + if (!b) + return NULL; + assert(jl_is_binding(b)); + return jl_atomic_load_relaxed(&b->partitions); +} + +JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr, size_t world); + +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; + +#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 +{ + while (1) { + if (!*bpart) + return encode_restriction(NULL, BINDING_KIND_GUARD); + jl_ptr_kind_union_t pku = jl_atomic_load_acquire(&(*bpart)->restriction); + if (!jl_bkind_is_some_import(decode_restriction_kind(pku))) + return pku; + *bnd = (jl_binding_t*)decode_restriction_value(pku); + *bpart = jl_get_binding_partition(*bnd, world); + } +} +#endif + STATIC_INLINE int is_anonfn_typename(char *name) { if (name[0] != '#' || name[1] == '#') diff --git a/src/method.c b/src/method.c index d890489c390f9..d4457b1549353 100644 --- a/src/method.c +++ b/src/method.c @@ -237,11 +237,9 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve if (fe_mod->istopmod && !strcmp(jl_symbol_name(fe_sym), "getproperty") && jl_is_symbol(s)) { if (eager_resolve || jl_binding_resolved_p(me_mod, me_sym)) { jl_binding_t *b = jl_get_binding(me_mod, me_sym); - if (b && b->constp) { - jl_value_t *v = jl_atomic_load_relaxed(&b->value); - if (v && jl_is_module(v)) - return jl_module_globalref((jl_module_t*)v, (jl_sym_t*)s); - } + jl_value_t *v = jl_get_binding_value_if_const(b); + if (v && jl_is_module(v)) + return jl_module_globalref((jl_module_t*)v, (jl_sym_t*)s); } } } @@ -254,7 +252,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve if (jl_binding_resolved_p(fe_mod, fe_sym)) { // look at some known called functions jl_binding_t *b = jl_get_binding(fe_mod, fe_sym); - if (b && b->constp && jl_atomic_load_relaxed(&b->value) == jl_builtin_tuple) { + if (jl_get_binding_value_if_const(b) == jl_builtin_tuple) { size_t j; for (j = 1; j < nargs; j++) { if (!jl_is_quotenode(jl_exprarg(e, j))) @@ -1124,29 +1122,24 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name return m; } -// empty generic function def -JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, - jl_module_t *module, - _Atomic(jl_value_t*) *bp, - jl_binding_t *bnd) +JL_DLLEXPORT void jl_check_gf(jl_value_t *gf, jl_sym_t *name) { - jl_value_t *gf = NULL; - - assert(name && bp); - if (bnd && jl_atomic_load_relaxed(&bnd->value) != NULL && !bnd->constp) + if (!jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(gf)) && !jl_is_type(gf)) jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); - gf = jl_atomic_load_relaxed(bp); - if (gf != NULL) { - if (!jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(gf)) && !jl_is_type(gf)) - jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); - } - if (bnd) - bnd->constp = 1; // XXX: use jl_declare_constant and jl_checked_assignment - if (gf == NULL) { - gf = (jl_value_t*)jl_new_generic_function(name, module); - jl_atomic_store(bp, gf); // TODO: fix constp assignment data race - if (bnd) jl_gc_wb(bnd, gf); +} + +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_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); return gf; } diff --git a/src/module.c b/src/module.c index bfe266ee424f5..7f03fc7e66a30 100644 --- a/src/module.c +++ b/src/module.c @@ -12,6 +12,23 @@ extern "C" { #endif +// In this translation unit and this translation unit only emit this symbol `extern` for use by julia +EXTERN_INLINE_DEFINE jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) JL_NOTSAFEPOINT; +EXTERN_INLINE_DEFINE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT; +extern inline enum jl_partition_kind decode_restriction_kind(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT; + +JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr, size_t world) +{ + if (!gr) + return NULL; + jl_binding_t *b = NULL; + if (gr) + b = gr->binding; + if (!b) + b = jl_get_module_binding(gr->mod, gr->name, 0); + return jl_get_binding_partition(b, world); +} + JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_names) { jl_task_t *ct = jl_current_task; @@ -161,37 +178,51 @@ static jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_bin jl_task_t *ct = jl_current_task; jl_globalref_t *g = (jl_globalref_t*)jl_gc_alloc(ct->ptls, sizeof(jl_globalref_t), jl_globalref_type); g->mod = mod; - jl_gc_wb(g, g->mod); + jl_gc_wb_fresh(g, g->mod); g->name = name; + jl_gc_wb_fresh(g, g->name); g->binding = b; + jl_gc_wb_fresh(g, g->binding); return g; } +static jl_binding_partition_t *new_binding_partition(void) +{ + jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type); + jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD)); + bpart->min_world = 0; + jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1); + jl_atomic_store_relaxed(&bpart->next, NULL); +#ifdef _P64 + bpart->reserved = 0; +#endif + return bpart; +} + static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) { jl_task_t *ct = jl_current_task; assert(jl_is_module(mod) && jl_is_symbol(name)); jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type); jl_atomic_store_relaxed(&b->value, NULL); - jl_atomic_store_relaxed(&b->owner, NULL); - jl_atomic_store_relaxed(&b->ty, NULL); + jl_atomic_store_relaxed(&b->partitions, NULL); b->globalref = NULL; - b->constp = 0; b->exportp = 0; b->publicp = 0; - b->imported = 0; b->deprecated = 0; - b->usingfailed = 0; - b->padding = 0; JL_GC_PUSH1(&b); b->globalref = jl_new_globalref(mod, name, b); + jl_gc_wb(b, b->globalref); + jl_binding_partition_t *bpart = new_binding_partition(); + jl_atomic_store_relaxed(&b->partitions, bpart); + jl_gc_wb(b, bpart); JL_GC_POP(); return b; } extern jl_mutex_t jl_modules_mutex; -static void check_safe_newbinding(jl_module_t *m, jl_sym_t *var) +extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var) { if (jl_current_task->ptls->in_pure_callback) jl_errorf("new globals cannot be created in a generated function"); @@ -222,14 +253,21 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) { jl_binding_t *b = jl_get_module_binding(m, var, 1); - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b) { - if (b2 == NULL) { - check_safe_newbinding(m, var); - if (!alloc) - jl_errorf("Global %s.%s does not exist and cannot be assigned. Declare it using `global` before attempting assignment.", jl_symbol_name(m->name), jl_symbol_name(var)); - } - if (b2 != NULL || (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b)) { + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); +retry: + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (decode_restriction_kind(pku) != BINDING_KIND_DECLARED) { + check_safe_newbinding(m, var); + if (!alloc) + jl_errorf("Global %s.%s does not exist and cannot be assigned. Declare it using `global` before attempting assignment.", jl_symbol_name(m->name), jl_symbol_name(var)); + } + jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)jl_any_type, BINDING_KIND_GLOBAL); + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) + goto retry; + jl_gc_wb_knownold(bpart, jl_any_type); + } else { jl_module_t *from = jl_binding_dbgmodule(b, m, var); if (from == m) jl_errorf("cannot assign a value to imported variable %s.%s", @@ -251,43 +289,88 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var return b->globalref->mod; // TODO: deprecate this? } +JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) +{ + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + return NULL; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return decode_restriction_value(pku); + return jl_atomic_load_relaxed(&b->value); +} + +JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) +{ + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + return NULL; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return decode_restriction_value(pku); + return jl_atomic_load(&b->value); +} + +JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b) +{ + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + return NULL; + if (!jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return NULL; + return decode_restriction_value(pku); +} + +typedef struct _modstack_t { + jl_module_t *m; + 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); + +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); + } + return jl_get_binding_value_seqcst(b); +} + // get binding for adding a method // like jl_get_binding_wr, but has different error paths and messages JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 1); - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b) { - if (b2 == NULL) - check_safe_newbinding(m, var); - if (b2 != NULL || (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b)) { - jl_value_t *f = jl_atomic_load_relaxed(&b2->value); - jl_module_t *from = jl_binding_dbgmodule(b, m, var); - if (f == NULL) { - // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from - jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); - } - // TODO: we might want to require explicitly importing types to add constructors - // or we might want to drop this error entirely - if (!b->imported && !(b2->constp && jl_is_type(f) && strcmp(jl_symbol_name(var), "=>") != 0)) { - jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (decode_restriction_kind(pku) != BINDING_KIND_DECLARED) { + check_safe_newbinding(m, var); } - return b2; + return b; + } + jl_value_t *f = jl_get_binding_value_if_const(b); + if (f == NULL) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from + jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); } + // TODO: we might want to require explicitly importing types to add constructors + // or we might want to drop this error entirely + if (decode_restriction_kind(pku) != BINDING_KIND_IMPORTED && !(f && jl_is_type(f) && strcmp(jl_symbol_name(var), "=>") != 0)) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + } + return b; } return b; } -typedef struct _modstack_t { - jl_module_t *m; - 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 inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; #ifndef __clang_gcanalyzer__ @@ -298,23 +381,28 @@ static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROO } #endif -static int eq_bindings(jl_binding_t *owner, jl_binding_t *alias) +static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_t world) { - assert(owner == jl_atomic_load_relaxed(&owner->owner)); - if (owner == alias) + jl_ptr_kind_union_t owner_pku = jl_atomic_load_relaxed(&owner->restriction); + assert(decode_restriction_kind(owner_pku) == BINDING_KIND_GLOBAL || + jl_bkind_is_some_constant(decode_restriction_kind(owner_pku))); + jl_binding_partition_t *alias_bpart = jl_get_binding_partition(alias, world); + if (owner == alias_bpart) return 1; - alias = jl_atomic_load_relaxed(&alias->owner); - if (owner == alias) + jl_ptr_kind_union_t alias_pku = jl_walk_binding_inplace(&alias, &alias_bpart, world); + if (jl_bkind_is_some_constant(decode_restriction_kind(owner_pku)) && + jl_bkind_is_some_constant(decode_restriction_kind(alias_pku)) && + decode_restriction_value(owner_pku) && + decode_restriction_value(alias_pku) == decode_restriction_value(owner_pku)) return 1; - if (owner->constp && alias->constp && jl_atomic_load_relaxed(&owner->value) && jl_atomic_load_relaxed(&alias->value) == jl_atomic_load_relaxed(&owner->value)) - return 1; - return 0; + return owner == alias_bpart; } // 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) { jl_binding_t *b = NULL; + jl_binding_partition_t *bpart = NULL; jl_module_t *owner = NULL; JL_LOCK(&m->lock); int i = (int)m->usings.len - 1; @@ -329,13 +417,17 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl if (tempb == NULL) // couldn't resolve; try next using (see issue #6105) continue; - assert(jl_atomic_load_relaxed(&tempb->owner) == tempb); - if (b != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempb, b)) { + jl_binding_partition_t *tempbpart = jl_get_binding_partition(tempb, jl_current_task->world_age); + jl_ptr_kind_union_t tempb_pku = jl_atomic_load_relaxed(&tempbpart->restriction); + assert(decode_restriction_kind(tempb_pku) == BINDING_KIND_GLOBAL || 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 (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); - tempb->usingfailed = 1; + tempbpart = jl_get_binding_partition(tempb, jl_current_task->world_age); + jl_atomic_store_release(&tempbpart->restriction, encode_restriction(NULL, BINDING_KIND_FAILED)); jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", jl_symbol_name(owner->name), @@ -347,6 +439,7 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl if (owner == NULL || !tempb->deprecated) { owner = imp; b = tempb; + bpart = tempbpart; } } } @@ -358,13 +451,14 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl // this might not be the same as the owner of the binding, since the binding itself may itself have been imported from elsewhere static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b && !b->imported) { + 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_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; - b = using_resolve_binding(m, var, &from, NULL, 0); - if (b) { - if (b2 == NULL || jl_atomic_load_relaxed(&b->owner) == jl_atomic_load_relaxed(&b2->owner)) + jl_binding_t *b2 = using_resolve_binding(m, var, &from, NULL, 0); + 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)) return from; // if we did not find it (or accidentally found a different one), ignore this } @@ -379,10 +473,16 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * { if (b == NULL) b = jl_get_module_binding(m, var, 1); - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 == NULL) { - if (b->usingfailed) - return NULL; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); +retry: + if (decode_restriction_kind(pku) == BINDING_KIND_FAILED) + return NULL; + if (decode_restriction_kind(pku) == BINDING_KIND_DECLARED) { + return b; + } + if (decode_restriction_kind(pku) == BINDING_KIND_GUARD) { + jl_binding_t *b2 = NULL; modstack_t top = { m, var, st }; modstack_t *tmp = st; for (; tmp != NULL; tmp = tmp->prev) { @@ -397,19 +497,17 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * return NULL; assert(from); JL_GC_PROMISE_ROOTED(from); // gc-analysis does not understand output parameters + JL_GC_PROMISE_ROOTED(b2); if (b2->deprecated) { - if (jl_atomic_load_relaxed(&b2->value) == jl_nothing) { + if (jl_get_binding_value(b2) == jl_nothing) { // silently skip importing deprecated values assigned to nothing (to allow later mutation) return NULL; } } // do a full import to prevent the result of this lookup from // changing, for example if this var is assigned to later. - jl_binding_t *owner = NULL; - if (!jl_atomic_cmpswap(&b->owner, &owner, b2)) { - // concurrent import - return owner; - } + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction((jl_value_t*)b2, BINDING_KIND_IMPLICIT))) + goto retry; if (b2->deprecated) { b->deprecated = 1; // we will warn about this below, but we might want to warn at the use sites too if (m != jl_main_module && m != jl_base_module && @@ -424,20 +522,26 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * jl_binding_dep_message(from, var, b2); } } + return b2; } - assert(jl_atomic_load_relaxed(&b2->owner) == b2); - return b2; + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + return b; } // get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_module_t *from = m; - if (b == NULL || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) + 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); - else - b = jl_atomic_load_relaxed(&b->owner); + bpart = jl_get_binding_partition(b, jl_current_task->world_age); + } + pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return NULL; return b; } @@ -445,13 +549,20 @@ JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (b == NULL) return jl_nothing; - b = jl_atomic_load_relaxed(&b->owner); - if (b == NULL) + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) return jl_nothing; - jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); - return ty ? ty : jl_nothing; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + // TODO: We would like to return the type of the constant, but + // currently code relies on this returning any to bypass conversion + // before an attempted assignment to a constant. + // return jl_typeof(jl_atomic_load_relaxed(&bpart->restriction)); + return (jl_value_t*)jl_any_type; + } + return decode_restriction_value(pku); } JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) @@ -482,7 +593,8 @@ JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 0); - return b && b->imported; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + return b && decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_IMPORTED; } extern const char *jl_filename; @@ -501,7 +613,7 @@ static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t jl_binding_t *dep_message_binding = jl_get_binding(m, jl_symbol(dep_binding_name)); jl_value_t *dep_message = NULL; if (dep_message_binding != NULL) - dep_message = jl_atomic_load_relaxed(&dep_message_binding->value); + dep_message = jl_get_binding_value(dep_message_binding); JL_GC_PUSH1(&dep_message); if (dep_message != NULL) { if (jl_is_string(dep_message)) { @@ -512,7 +624,7 @@ static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t } } else { - jl_value_t *v = jl_atomic_load_relaxed(&b->value); + jl_value_t *v = jl_get_binding_value(b); dep_message = v; // use as gc-root if (v) { if (jl_is_type(v) || jl_is_module(v)) { @@ -549,9 +661,12 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_symbol_name(to->name)); } else { - assert(jl_atomic_load_relaxed(&b->owner) == b); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + assert(decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(decode_restriction_kind(pku))); + (void)pku; if (b->deprecated) { - if (jl_atomic_load_relaxed(&b->value) == jl_nothing) { + if (jl_get_binding_value(b) == jl_nothing) { // silently skip importing deprecated values assigned to nothing (to allow later mutation) return; } @@ -575,17 +690,28 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, // importing a binding on top of itself. harmless. return; } - jl_binding_t *ownerto = NULL; - if (jl_atomic_cmpswap(&bto->owner, &ownerto, b)) { - bto->imported |= (explici != 0); + jl_binding_partition_t *btopart = jl_get_binding_partition(bto, jl_current_task->world_age); + jl_ptr_kind_union_t bto_pku = jl_atomic_load_relaxed(&btopart->restriction); +retry: + if (decode_restriction_kind(bto_pku) == BINDING_KIND_GUARD || + decode_restriction_kind(bto_pku) == BINDING_KIND_IMPLICIT || + decode_restriction_kind(bto_pku) == BINDING_KIND_FAILED) { + + jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)b, (explici != 0) ? BINDING_KIND_IMPORTED : BINDING_KIND_EXPLICIT); + if (!jl_atomic_cmpswap(&btopart->restriction, &bto_pku, new_pku)) + goto retry; bto->deprecated |= b->deprecated; // we already warned about this above, but we might want to warn at the use sites too } else { - if (eq_bindings(b, bto)) { - // already imported - bto->imported |= (explici != 0); + if (eq_bindings(bpart, bto, jl_current_task->world_age)) { + // already imported - potentially upgrade to _IMPORTED or _EXPLICIT + if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { + jl_ptr_kind_union_t new_pku = encode_restriction(decode_restriction_value(bto_pku), (explici != 0) ? BINDING_KIND_IMPORTED : BINDING_KIND_EXPLICIT); + if (!jl_atomic_cmpswap(&btopart->restriction, &bto_pku, new_pku)) + goto retry; + } } - else if (ownerto != bto) { + else if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { // already imported from somewhere else jl_printf(JL_STDERR, "WARNING: ignoring conflicting import of %s.%s into %s\n", @@ -647,18 +773,24 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; - if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (b->exportp && (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(pku) == BINDING_KIND_IMPORTED)) { jl_sym_t *var = b->globalref->name; jl_binding_t *tob = jl_get_module_binding(to, var, 0); - if (tob && jl_atomic_load_relaxed(&tob->owner) != NULL && - // don't warn for conflicts with the module name itself. - // see issue #4715 - var != to->name && - !eq_bindings(jl_atomic_load_relaxed(&tob->owner), b)) { - jl_printf(JL_STDERR, - "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", - jl_symbol_name(from->name), jl_symbol_name(var), - jl_symbol_name(to->name)); + if (tob) { + jl_binding_partition_t *tobpart = jl_get_binding_partition(tob, jl_current_task->world_age); + jl_ptr_kind_union_t tobpku = jl_walk_binding_inplace(&tob, &tobpart, jl_current_task->world_age); + if (tob && decode_restriction_kind(tobpku) != BINDING_KIND_GUARD && + // don't warn for conflicts with the module name itself. + // see issue #4715 + var != to->name && + !eq_bindings(tobpart, b, jl_current_task->world_age)) { + jl_printf(JL_STDERR, + "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", + jl_symbol_name(from->name), jl_symbol_name(var), + jl_symbol_name(to->name)); + } } } table = jl_atomic_load_relaxed(&from->bindings); @@ -683,14 +815,23 @@ JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // unlike most queries here, this is currently seq_cst { - jl_binding_t *b = allow_import ? jl_get_binding(m, var) : jl_get_module_binding(m, var, 0); - return b && (jl_atomic_load_relaxed(&b->owner) == b) && (jl_atomic_load(&b->value) != NULL); + jl_binding_t *b = jl_get_module_binding(m, var, allow_import); + if (!b) + return 0; + if (!allow_import) { + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + if (!bpart || jl_bkind_is_some_import(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))) + return 0; + return jl_get_binding_value(b) != NULL; + } + return jl_reresolve_binding_value_seqcst(b) != NULL; } JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 0); - return b && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + return b && (b->exportp || decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GLOBAL); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) @@ -708,7 +849,11 @@ JL_DLLEXPORT int jl_module_public_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_binding_t *b = jl_get_module_binding(m, var, 0); - return b && jl_atomic_load_relaxed(&b->owner) != NULL; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + if (!bpart) + return 0; + enum jl_partition_kind kind = decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); + return kind == BINDING_KIND_DECLARED || !jl_bkind_is_some_guard(kind); } static uint_t bindingkey_hash(size_t idx, jl_value_t *data) @@ -736,6 +881,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, ssize_t idx = jl_smallintset_lookup(bindingkeyset, bindingkey_eq, var, (jl_value_t*)bindings, hv, 0); // acquire if (idx != -1) { jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed + JL_GC_PROMISE_ROOTED(b); if (locked) JL_UNLOCK(&m->lock); return b; @@ -780,7 +926,7 @@ 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); // ignores b->deprecated - return b == NULL ? NULL : jl_atomic_load_relaxed(&b->value); + return b == NULL ? NULL : jl_get_binding_value(b); } JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) @@ -791,7 +937,7 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) // XXX: this only considers if the original is deprecated, not the binding in m if (b->deprecated) jl_binding_deprecation_warning(m, var, b); - return jl_atomic_load_relaxed(&b->value); + return jl_get_binding_value(b); } JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) @@ -804,43 +950,33 @@ 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_t *b2 = NULL; - if (!jl_atomic_cmpswap(&bp->owner, &b2, bp) && b2 != bp) - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); - if (jl_atomic_load_relaxed(&bp->value) == NULL) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); - uint8_t constp = 0; - // if (jl_atomic_cmpswap(&bp->constp, &constp, 1)) { - if (constp = bp->constp, bp->constp = 1, constp == 0) { - jl_value_t *old = NULL; - if (jl_atomic_cmpswap(&bp->value, &old, val)) { - jl_gc_wb(bp, val); - return; - } - } - } - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); + jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age); + jl_atomic_store_release(&bpart->restriction, encode_restriction(val, BINDING_KIND_CONST)); + jl_gc_wb(bpart, val); } 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); - return b && b->constp; + 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_binding_t *b = gr->binding; b = jl_resolve_owner(b, gr->mod, gr->name, NULL); - return b && jl_atomic_load_relaxed(&b->value) != NULL; + return b && jl_get_binding_value(b) != NULL; } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - return b && b->constp; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + return b && jl_bkind_is_some_constant(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction))); } // set the deprecated flag for a binding: @@ -870,7 +1006,6 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b if (b->deprecated == 1 && jl_options.depwarn) { if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) jl_printf(JL_STDERR, "WARNING: "); - assert(jl_atomic_load_relaxed(&b->owner) == b); jl_printf(JL_STDERR, "%s.%s is deprecated", jl_symbol_name(m->name), jl_symbol_name(s)); jl_binding_dep_message(m, s, b); @@ -889,39 +1024,29 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b } } -jl_value_t *jl_check_binding_wr(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED, int reassign) +jl_value_t *jl_check_binding_wr(jl_binding_t *b JL_PROPAGATES_ROOT, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED, int reassign) { - jl_value_t *old_ty = NULL; - if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type)) { - if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { - JL_GC_PUSH1(&rhs); // callee-rooted - if (!jl_isa(rhs, old_ty)) - jl_errorf("cannot assign an incompatible value to the global %s.%s.", - jl_symbol_name(mod->name), jl_symbol_name(var)); - JL_GC_POP(); - } - } - else { - old_ty = (jl_value_t*)jl_any_type; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + assert(!jl_bkind_is_some_guard(decode_restriction_kind(pku)) && !jl_bkind_is_some_import(decode_restriction_kind(pku))); + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + jl_value_t *old = decode_restriction_value(pku); + if (jl_egal(rhs, old)) + return NULL; + if (jl_typeof(rhs) == jl_typeof(old)) + jl_errorf("invalid redefinition of constant %s.%s. This redefinition may be permitted using the `const` keyword.", + jl_symbol_name(mod->name), jl_symbol_name(var)); + else + jl_errorf("invalid redefinition of constant %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(var)); } - if (b->constp) { - if (reassign) { - jl_value_t *old = NULL; - if (jl_atomic_cmpswap(&b->value, &old, rhs)) { - jl_gc_wb(b, rhs); - return NULL; - } - if (jl_egal(rhs, old)) - return NULL; - if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) - reassign = 0; - else - jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(mod->name), jl_symbol_name(var)); - } - if (!reassign) - jl_errorf("invalid redefinition of constant %s.%s", - jl_symbol_name(mod->name), jl_symbol_name(var)); + jl_value_t *old_ty = decode_restriction_value(pku); + if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { + JL_GC_PUSH1(&rhs); // callee-rooted + if (!jl_isa(rhs, old_ty)) + jl_errorf("cannot assign an incompatible value to the global %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(var)); + JL_GC_POP(); } return old_ty; } @@ -952,12 +1077,13 @@ JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, j JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs) { - jl_value_t *ty = NULL; - if (jl_atomic_cmpswap_relaxed(&b->ty, &ty, (jl_value_t*)jl_any_type)) - ty = (jl_value_t*)jl_any_type; - if (b->constp) + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + assert(!jl_bkind_is_some_guard(decode_restriction_kind(pku)) && !jl_bkind_is_some_import(decode_restriction_kind(pku))); + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) jl_errorf("invalid redefinition of constant %s.%s", jl_symbol_name(mod->name), jl_symbol_name(var)); + jl_value_t *ty = decode_restriction_value(pku); return modify_value(ty, &b->value, (jl_value_t*)b, op, rhs, 1, mod, var); } @@ -970,16 +1096,6 @@ JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod return old; } -JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var) -{ - // n.b. jl_get_binding_wr should have ensured b->owner == b as mod.var - if (jl_atomic_load_relaxed(&b->owner) != b || (jl_atomic_load_relaxed(&b->value) != NULL && !b->constp)) { - jl_errorf("cannot declare %s.%s constant; it already has a value", - jl_symbol_name(mod->name), jl_symbol_name(var)); - } - b->constp = 1; -} - JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m) { JL_LOCK(&m->lock); @@ -996,11 +1112,6 @@ JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m) return (jl_value_t*)a; } -uint8_t _binding_is_from_explicit_using(jl_binding_t *b) { - jl_binding_t *owner = jl_atomic_load_relaxed(&b->owner); - return (owner != NULL && owner != b && !b->imported); -} - void _append_symbol_to_bindings_array(jl_array_t* a, jl_sym_t *name) { jl_array_grow_end(a, 1); //XXX: change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: @@ -1017,10 +1128,12 @@ void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, i jl_sym_t *asname = b->globalref->name; int hidden = jl_symbol_name(asname)[0]=='#'; int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym)); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + enum jl_partition_kind kind = decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); if (((b->publicp) || - (imported && b->imported) || - (usings && _binding_is_from_explicit_using(b)) || - (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || main_public))) && + (imported && (kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_IMPORTED)) || + (usings && kind == BINDING_KIND_EXPLICIT) || + ((kind == BINDING_KIND_GLOBAL || kind == BINDING_KIND_CONST || kind == BINDING_KIND_DECLARED) && (all || main_public))) && (all || (!b->deprecated && !hidden))) _append_symbol_to_bindings_array(a, asname); } @@ -1095,8 +1208,10 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; - if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) - jl_atomic_store_relaxed(&b->owner, NULL); + 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_IMPLICIT) { + jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD)); + } } JL_UNLOCK(&m->lock); } diff --git a/src/rtutils.c b/src/rtutils.c index a60597827b92c..a6a7fd5614de0 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -553,7 +553,7 @@ JL_DLLEXPORT jl_value_t *jl_stderr_obj(void) JL_NOTSAFEPOINT if (jl_base_module == NULL) return NULL; jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr"), 0); - return stderr_obj ? jl_atomic_load_relaxed(&stderr_obj->value) : NULL; + return stderr_obj ? jl_get_binding_value(stderr_obj) : NULL; } // toys for debugging --------------------------------------------------------- @@ -648,12 +648,10 @@ static int is_globname_binding(jl_value_t *v, jl_datatype_t *dv) JL_NOTSAFEPOINT jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; if (globname && dv->name->module) { jl_binding_t *b = jl_get_module_binding(dv->name->module, globname, 0); - if (b && jl_atomic_load_relaxed(&b->owner) && b->constp) { - jl_value_t *bv = jl_atomic_load_relaxed(&b->value); - // The `||` makes this function work for both function instances and function types. - if (bv == v || jl_typeof(bv) == v) - return 1; - } + jl_value_t *bv = jl_get_binding_value_if_const(b); + // The `||` makes this function work for both function instances and function types. + if (bv && (bv == v || jl_typeof(bv) == v)) + return 1; } return 0; } diff --git a/src/staticdata.c b/src/staticdata.c index 1fb8c8ec79460..6dfe5e91a9c55 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -100,7 +100,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 191 +#define NUM_TAGS 192 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -122,6 +122,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_array_type); INSERT_TAG(jl_expr_type); INSERT_TAG(jl_binding_type); + INSERT_TAG(jl_binding_partition_type); INSERT_TAG(jl_globalref_type); INSERT_TAG(jl_string_type); INSERT_TAG(jl_module_type); @@ -349,6 +350,18 @@ arraylist_t eytzinger_idxs; static uintptr_t img_min; static uintptr_t img_max; +// HT_NOTFOUND is a valid integer ID, so we store the integer ids mangled. +// This pair of functions mangles/demanges +static size_t from_seroder_entry(void *entry) +{ + return (size_t)((char*)entry - (char*)HT_NOTFOUND - 1); +} + +static void *to_seroder_entry(size_t idx) +{ + return (void*)((char*)HT_NOTFOUND + 1 + idx); +} + static int ptr_cmp(const void *l, const void *r) { uintptr_t left = *(const uintptr_t*)l; @@ -563,6 +576,8 @@ enum RefTags { ExternalLinkage // reference to some other pkgimage }; +#define SYS_EXTERNAL_LINK_UNIT sizeof(void*) + // calling conventions for internal entry points. // this is used to set the method-instance->invoke field typedef enum { @@ -768,7 +783,7 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ if ((void*)b == jl_nothing) break; jl_sym_t *name = b->globalref->name; - if (name == jl_docmeta_sym && jl_atomic_load_relaxed(&b->value)) + if (name == jl_docmeta_sym && jl_get_binding_value(b)) record_field_change((jl_value_t**)&b->value, jl_nothing); } } @@ -922,14 +937,17 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ else if (jl_typetagis(v, jl_module_tag << 4)) { jl_queue_module_for_serialization(s, (jl_module_t*)v); } + else if (jl_is_binding_partition(v)) { + jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; + jl_queue_for_serialization_(s, decode_restriction_value(jl_atomic_load_relaxed(&bpart->restriction)), 1, immediate); + jl_queue_for_serialization_(s, get_replaceable_field((jl_value_t**)&bpart->next, 0), 1, immediate); + } else if (layout->nfields > 0) { char *data = (char*)jl_data_ptr(v); size_t i, np = layout->npointers; for (i = 0; i < np; i++) { uint32_t ptr = jl_ptr_offset(t, i); int mutabl = t->name->mutabl; - if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field - mutabl = 0; jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], mutabl); jl_queue_for_serialization_(s, fld, 1, immediate); } @@ -943,7 +961,7 @@ done_fields: ; arraylist_push(&serialization_queue, (void*) v); size_t idx = serialization_queue.len - 1; assert(serialization_queue.len < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many items to serialize"); - *bp = (void*)((char*)HT_NOTFOUND + 1 + idx); + *bp = to_seroder_entry(idx); // DataType is very unusual, in that some of the fields need to be pre-order, and some // (notably super) must not be (even if `jl_queue_for_serialization_` would otherwise @@ -1064,8 +1082,8 @@ static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_ // We found the sysimg/pkg that this item links against // Compute the relocation code size_t offset = (uintptr_t)v - (uintptr_t)jl_linkage_blobs.items[2*i]; - offset /= sizeof(void*); - assert(offset < ((uintptr_t)1 << DEPS_IDX_OFFSET) && "offset to external image too large"); + assert((offset % SYS_EXTERNAL_LINK_UNIT) == 0); + offset /= SYS_EXTERNAL_LINK_UNIT; assert(n_linkage_blobs() == jl_array_nrows(s->buildid_depmods_idxs)); size_t depsidx = jl_array_data(s->buildid_depmods_idxs, uint32_t)[i]; // map from build_id_idx -> deps_idx assert(depsidx < INT32_MAX); @@ -1077,6 +1095,7 @@ static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_ jl_array_grow_end(link_ids, 1); uint32_t *link_id_data = jl_array_data(link_ids, uint32_t); // wait until after the `grow` link_id_data[jl_array_nrows(link_ids) - 1] = depsidx; + assert(offset < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "offset to external image too large"); return ((uintptr_t)ExternalLinkage << RELOC_TAG_OFFSET) + offset; } return 0; @@ -1089,19 +1108,19 @@ static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_ static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v, jl_array_t *link_ids) JL_NOTSAFEPOINT { assert(v != NULL && "cannot get backref to NULL object"); - void *idx = HT_NOTFOUND; if (jl_is_symbol(v)) { void **pidx = ptrhash_bp(&symbol_table, v); - idx = *pidx; + void *idx = *pidx; if (idx == HT_NOTFOUND) { size_t l = strlen(jl_symbol_name((jl_sym_t*)v)); write_uint32(s->symbols, l); ios_write(s->symbols, jl_symbol_name((jl_sym_t*)v), l + 1); size_t offset = ++nsym_tag; assert(offset < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many symbols"); - idx = (void*)((char*)HT_NOTFOUND + ((uintptr_t)SymbolRef << RELOC_TAG_OFFSET) + offset); + idx = to_seroder_entry(offset - 1); *pidx = idx; } + return ((uintptr_t)SymbolRef << RELOC_TAG_OFFSET) + from_seroder_entry(idx); } else if (v == (jl_value_t*)s->ptls->root_task) { return (uintptr_t)TagRef << RELOC_TAG_OFFSET; @@ -1129,17 +1148,15 @@ static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v, jl_array_t * assert(item && "no external linkage identified"); return item; } + void *idx = ptrhash_get(&serialization_order, v); if (idx == HT_NOTFOUND) { - idx = ptrhash_get(&serialization_order, v); - if (idx == HT_NOTFOUND) { - jl_(jl_typeof(v)); - jl_(v); - } - assert(idx != HT_NOTFOUND && "object missed during jl_queue_for_serialization pass"); - assert(idx != (void*)(uintptr_t)-1 && "object missed during jl_insert_into_serialization_queue pass"); - assert(idx != (void*)(uintptr_t)-2 && "object missed during jl_insert_into_serialization_queue pass"); + jl_(jl_typeof(v)); + jl_(v); } - return (char*)idx - 1 - (char*)HT_NOTFOUND; + assert(idx != HT_NOTFOUND && "object missed during jl_queue_for_serialization pass"); + assert(idx != (void*)(uintptr_t)-1 && "object missed during jl_insert_into_serialization_queue pass"); + assert(idx != (void*)(uintptr_t)-2 && "object missed during jl_insert_into_serialization_queue pass"); + return ((uintptr_t)DataRef << RELOC_TAG_OFFSET) + from_seroder_entry(idx); } @@ -1341,7 +1358,15 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED if (s->incremental) { if (needs_uniquing(v)) { - if (jl_is_method_instance(v)) { + if (jl_typetagis(v, jl_binding_type)) { + jl_binding_t *b = (jl_binding_t*)v; + if (b->globalref == NULL) + jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity + write_pointerfield(s, (jl_value_t*)b->globalref->mod); + write_pointerfield(s, (jl_value_t*)b->globalref->name); + continue; + } + else if (jl_is_method_instance(v)) { assert(f == s->s); jl_method_instance_t *mi = (jl_method_instance_t*)v; write_pointerfield(s, mi->def.value); @@ -1364,17 +1389,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (needs_recaching(v)) { arraylist_push(jl_is_datatype(v) ? &s->fixup_types : &s->fixup_objs, (void*)reloc_offset); } - else if (jl_typetagis(v, jl_binding_type)) { - jl_binding_t *b = (jl_binding_t*)v; - if (b->globalref == NULL) - jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity - // Assign type Any to any owned bindings that don't have a type. - // We don't want these accidentally managing to diverge later in different compilation units. - if (jl_atomic_load_relaxed(&b->owner) == b) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - } - } } // write data @@ -1560,6 +1574,26 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED ios_write(s->const_data, (char*)pdata, nb); write_pointer(f); } + else if (jl_is_binding_partition(v)) { + jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + jl_value_t *restriction_val = decode_restriction_value(pku); + static_assert(offsetof(jl_binding_partition_t, restriction) == 0, "BindingPartition layout mismatch"); + write_pointerfield(s, restriction_val); +#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)); + 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) + static_assert(sizeof(jl_binding_partition_t) == 5*sizeof(void*), "BindingPartition layout mismatch"); +#else + write_uint(f, 0); + static_assert(sizeof(jl_binding_partition_t) == 6*sizeof(void*), "BindingPartition layout mismatch"); +#endif + } else { // Generic object::DataType serialization by field const char *data = (const char*)v; @@ -1586,8 +1620,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED for (i = 0; i < np; i++) { size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); int mutabl = t->name->mutabl; - if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field - mutabl = 0; jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], mutabl); size_t fld_pos = offset + reloc_offset; if (fld != NULL) { @@ -1763,7 +1795,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } } void *superidx = ptrhash_get(&serialization_order, dt->super); - if (s->incremental && superidx != HT_NOTFOUND && (char*)superidx - 1 - (char*)HT_NOTFOUND > item && needs_uniquing((jl_value_t*)dt->super)) + if (s->incremental && superidx != HT_NOTFOUND && from_seroder_entry(superidx) > item && needs_uniquing((jl_value_t*)dt->super)) arraylist_push(&s->uniquing_super, dt->super); } else if (jl_is_typename(v)) { @@ -1957,7 +1989,7 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas assert(s->buildid_depmods_idxs && depsidx < jl_array_len(s->buildid_depmods_idxs)); size_t i = jl_array_data(s->buildid_depmods_idxs, uint32_t)[depsidx]; assert(2*i < jl_linkage_blobs.len); - return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); + return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*SYS_EXTERNAL_LINK_UNIT; } case ExternalLinkage: { assert(link_ids); @@ -1968,7 +2000,7 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas assert(depsidx < jl_array_len(s->buildid_depmods_idxs)); size_t i = jl_array_data(s->buildid_depmods_idxs, uint32_t)[depsidx]; assert(2*i < jl_linkage_blobs.len); - return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); + return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*SYS_EXTERNAL_LINK_UNIT; } } abort(); @@ -2352,11 +2384,11 @@ static jl_svec_t *jl_prune_type_cache_hash(jl_svec_t *cache) JL_GC_DISABLED void *idx = ptrhash_get(&serialization_order, cache); assert(idx != HT_NOTFOUND && idx != (void*)(uintptr_t)-1); - assert(serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] == cache); + assert(serialization_queue.items[from_seroder_entry(idx)] == cache); cache = cache_rehash_set(cache, sz); // redirect all references to the old cache to relocate to the new cache object ptrhash_put(&serialization_order, cache, idx); - serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] = cache; + serialization_queue.items[from_seroder_entry(idx)] = cache; return cache; } @@ -3561,6 +3593,19 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); mod->usings.items = newitems; } + // Move the binding bits back to their correct place +#ifdef _P64 + jl_svec_t *table = jl_atomic_load_relaxed(&mod->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); + if ((jl_value_t*)b == jl_nothing) + continue; + jl_binding_partition_t *bpart = jl_atomic_load_relaxed(&b->partitions); + jl_atomic_store_relaxed(&bpart->restriction, + encode_restriction((jl_value_t*)jl_atomic_load_relaxed(&bpart->restriction), bpart->reserved)); + bpart->reserved = 0; + } +#endif } else { abort(); diff --git a/src/support/dtypes.h b/src/support/dtypes.h index 57f4fa99f0016..6513370da4dae 100644 --- a/src/support/dtypes.h +++ b/src/support/dtypes.h @@ -123,6 +123,13 @@ typedef intptr_t ssize_t; #define STATIC_INLINE static inline #define FORCE_INLINE static inline __attribute__((always_inline)) +#ifdef _OS_WINDOWS_ +#define EXTERN_INLINE_DECLARE inline +#else +#define EXTERN_INLINE_DECLARE inline __attribute__ ((visibility("default"))) +#endif +#define EXTERN_INLINE_DEFINE extern inline JL_DLLEXPORT + #if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) # define NOINLINE __declspec(noinline) # define NOINLINE_DECL(f) __declspec(noinline) f diff --git a/src/toplevel.c b/src/toplevel.c index 85d922016a4f8..5d17a3fcf89a7 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -155,25 +155,31 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } } else { - jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); - jl_declare_constant(b, parent_module, name); - jl_value_t *old = NULL; - if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { - if (!jl_is_module(old)) { - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); + jl_binding_t *b = jl_get_module_binding(parent_module, name, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); + jl_ptr_kind_union_t pku = encode_restriction(NULL, BINDING_KIND_CONST); + jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)newm, BINDING_KIND_CONST); + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) { + if (decode_restriction_kind(pku) != BINDING_KIND_CONST) { + jl_declare_constant_val(b, parent_module, name, (jl_value_t*)newm); + } else { + // As a special exception allow binding replacement of modules + if (!jl_is_module(decode_restriction_value(pku))) { + jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); + } + if (jl_generating_output()) + jl_errorf("cannot replace module %s during compilation", jl_symbol_name(name)); + jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(name)); + pku = jl_atomic_exchange(&bpart->restriction, new_pku); + } + jl_gc_wb(bpart, newm); + if (decode_restriction_value(pku) != NULL && jl_is_module(decode_restriction_value(pku))) { + // create a hidden gc root for the old module + JL_LOCK(&jl_modules_mutex); + uintptr_t *refcnt = (uintptr_t*)ptrhash_bp(&jl_current_modules, decode_restriction_value(pku)); + *refcnt += 1; + JL_UNLOCK(&jl_modules_mutex); } - if (jl_generating_output()) - jl_errorf("cannot replace module %s during compilation", jl_symbol_name(name)); - jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(name)); - old = jl_atomic_exchange(&b->value, (jl_value_t*)newm); - } - jl_gc_wb(b, newm); - if (old != NULL) { - // create a hidden gc root for the old module - JL_LOCK(&jl_modules_mutex); - uintptr_t *refcnt = (uintptr_t*)ptrhash_bp(&jl_current_modules, (void*)old); - *refcnt += 1; - JL_UNLOCK(&jl_modules_mutex); } } @@ -216,27 +222,6 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } ct->world_age = last_age; -#if 0 - // some optional post-processing steps - size_t i; - jl_svec_t *table = jl_atomic_load_relaxed(&newm->bindings); - for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); - if ((void*)b != jl_nothing) { - // remove non-exported macros - if (jl_symbol_name(b->name)[0]=='@' && - !b->exportp && b->owner == b) - b->value = NULL; - // error for unassigned exports - /* - if (b->exportp && b->owner==b && b->value==NULL) - jl_errorf("identifier %s exported from %s is not initialized", - jl_symbol_name(b->name), jl_symbol_name(newm->name)); - */ - } - } -#endif - JL_LOCK(&jl_modules_mutex); uintptr_t *refcnt = (uintptr_t*)ptrhash_bp(&jl_current_modules, (void*)newm); assert(*refcnt > (uintptr_t)HT_NOTFOUND); @@ -308,18 +293,38 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f return args[0]; } -void jl_binding_set_type(jl_binding_t *b, jl_value_t *ty, int error) +void jl_binding_set_type(jl_binding_t *b, jl_module_t *mod, jl_sym_t *sym, jl_value_t *ty) { - jl_value_t *old_ty = NULL; - if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) { - jl_gc_wb(b, ty); - } - else if (error && !jl_types_equal(ty, old_ty)) { - jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", - jl_symbol_name(jl_globalref_mod(b->globalref)->name), jl_symbol_name(jl_globalref_name(b->globalref))); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + jl_ptr_kind_union_t new_pku = encode_restriction(ty, BINDING_KIND_GLOBAL); + while (1) { + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) + break; + continue; + } else { + jl_errorf("cannot set type for imported global %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(sym)); + } + } + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + jl_errorf("cannot set type for imported constant %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(sym)); + } + jl_value_t *old_ty = decode_restriction_value(pku); + if (!jl_types_equal(ty, old_ty)) { + jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", + jl_symbol_name(mod->name), jl_symbol_name(sym)); + } + if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) + break; } + jl_gc_wb(bpart, ty); } +extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var); void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { // create uninitialized mutable binding for "global x" decl sometimes or probably jl_module_t *gm; @@ -334,11 +339,16 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { gm = m; gs = (jl_sym_t*)arg; } - if (!jl_binding_resolved_p(gm, gs) || set_type) { - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - if (set_type) { - jl_binding_set_type(b, set_type, 1); - } + jl_binding_t *b = jl_get_module_binding(gm, gs, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + while (decode_restriction_kind(pku) == BINDING_KIND_GUARD || decode_restriction_kind(pku) == BINDING_KIND_FAILED) { + check_safe_newbinding(gm, gs); + if (jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(NULL, BINDING_KIND_DECLARED))) + break; + } + if (set_type) { + jl_binding_set_type(b, gm, gs, set_type); } } @@ -413,9 +423,7 @@ static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int jl_sym_t *name = jl_globalref_name(f); if (jl_binding_resolved_p(mod, name)) { jl_binding_t *b = jl_get_binding(mod, name); - if (b && b->constp) { - called = jl_atomic_load_relaxed(&b->value); - } + called = jl_get_binding_value_if_const(b); } } else if (jl_is_quotenode(f)) { @@ -645,21 +653,16 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym assert(m); jl_sym_t *name = asname ? asname : import->name; // TODO: this is a bit race-y with what error message we might print - jl_binding_t *b = jl_get_module_binding(m, name, 0); - jl_binding_t *b2; - if (b != NULL && (b2 = jl_atomic_load_relaxed(&b->owner)) != NULL) { - if (b2->constp && jl_atomic_load_relaxed(&b2->value) == (jl_value_t*)import) - return; - if (b2 != b) - jl_errorf("importing %s into %s conflicts with an existing global", - jl_symbol_name(name), jl_symbol_name(m->name)); - } - else { - b = jl_get_binding_wr(m, name, 1); + jl_binding_t *b = jl_get_module_binding(m, name, 1); + if (jl_get_binding_value_if_const(b) == (jl_value_t*)import) + return; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (decode_restriction_kind(pku) != BINDING_KIND_GUARD && decode_restriction_kind(pku) != BINDING_KIND_FAILED) { + jl_errorf("importing %s into %s conflicts with an existing global", + jl_symbol_name(name), jl_symbol_name(m->name)); } - jl_declare_constant(b, m, name); - jl_checked_assignment(b, m, name, (jl_value_t*)import); - b->imported = 1; + jl_declare_constant_val2(b, m, name, (jl_value_t*)import, BINDING_KIND_CONST_IMPORT); } // in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists @@ -675,7 +678,7 @@ static jl_module_t *eval_import_from(jl_module_t *m JL_PROPAGATES_ROOT, jl_expr_ jl_module_t *from = eval_import_path(m, NULL, path->args, &name, keyword); if (name != NULL) { from = (jl_module_t*)jl_eval_global_var(from, name); - if (!jl_is_module(from)) + if (!from || !jl_is_module(from)) jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(name)); } return from; @@ -721,10 +724,49 @@ static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, con JL_GC_POP(); } -JL_DLLEXPORT void jl_declare_constant_val(jl_binding_t *b, jl_module_t *gm, jl_sym_t *gs, jl_value_t *val) +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_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + int did_warn = 0; + while (1) { + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (!val) + return bpart; + jl_value_t *old = decode_restriction_value(pku); + if (jl_egal(val, old)) + break; + if (!did_warn) { + if (jl_typeof(val) != jl_typeof(old) || jl_is_type(val) || jl_is_module(val)) + jl_errorf("invalid redefinition of constant %s.%s", + jl_symbol_name(mod->name), + jl_symbol_name(var)); + else + jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", + jl_symbol_name(mod->name), + jl_symbol_name(var)); + did_warn = 1; + } + } else if (!jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_import(decode_restriction_kind(pku))) { + jl_errorf("cannot declare %s.%s constant; it was already declared as an import", + jl_symbol_name(mod->name), jl_symbol_name(var)); + } else { + jl_errorf("cannot declare %s.%s constant; it was already declared global", + jl_symbol_name(mod->name), jl_symbol_name(var)); + } + } + if (jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { + jl_gc_wb(bpart, val); + break; + } + } + 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) { - jl_declare_constant(b, gm, gs); - jl_checked_assignment(b, gm, gs, val); + return jl_declare_constant_val2(b, mod, var, val, BINDING_KIND_CONST); } JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t *val) @@ -740,12 +782,8 @@ JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t gm = m; gs = (jl_sym_t*)arg; } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - if (val) { - jl_declare_constant_val(b, gm, gs, val); - } else { - jl_declare_constant(b, gm, gs); - } + jl_binding_t *b = jl_get_module_binding(gm, gs, 1); + jl_declare_constant_val(b, gm, gs, val); } JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 585ff1aa775b7..ddf2f55d0b9f7 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -33,35 +33,19 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) if isdefined(ex, :scope) scope = ex.scope if scope isa Module - bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding - if isdefined(bnd, :owner) - owner = bnd.owner - if owner === bnd - print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.") - end + bpart = Base.lookup_binding_partition(Base.get_world_counter(), GlobalRef(scope, var)) + kind = Base.binding_kind(bpart) + if kind === Base.BINDING_KIND_GLOBAL || kind === Base.BINDING_KIND_CONST || kind == Base.BINDING_KIND_DECLARED + print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.") + elseif kind === Base.BINDING_KIND_FAILED + print(io, "\nHint: It looks like two or more modules export different ", + "bindings with this name, resulting in ambiguity. Try explicitly ", + "importing it from a particular module, or qualifying the name ", + "with the module it should come from.") + elseif kind === Base.BINDING_KIND_GUARD + print(io, "\nSuggestion: check for spelling errors or missing imports.") else - owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var) - if C_NULL == owner - # No global of this name exists in this module. - # This is the common case, so do not print that information. - # It could be the binding was exported by two modules, which we can detect - # by the `usingfailed` flag in the binding: - if isdefined(bnd, :flags) && Bool(bnd.flags >> 4 & 1) # magic location of the `usingfailed` flag - print(io, "\nHint: It looks like two or more modules export different ", - "bindings with this name, resulting in ambiguity. Try explicitly ", - "importing it from a particular module, or qualifying the name ", - "with the module it should come from.") - else - print(io, "\nSuggestion: check for spelling errors or missing imports.") - end - owner = bnd - else - owner = unsafe_pointer_to_objref(owner)::Core.Binding - end - end - if owner !== bnd - # this could use jl_binding_dbgmodule for the exported location in the message too - print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.") + print(io, "\nSuggestion: this global was defined as `$(bpart.restriction.globalref)` but not assigned a value.") end elseif scope === :static_parameter print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.") diff --git a/stdlib/REPL/src/precompile.jl b/stdlib/REPL/src/precompile.jl index 7a4044b7f4c6d..a6effb9f013fc 100644 --- a/stdlib/REPL/src/precompile.jl +++ b/stdlib/REPL/src/precompile.jl @@ -100,6 +100,7 @@ let redirect_stdout(isopen(orig_stdout) ? orig_stdout : devnull) close(pts) end + Base.errormonitor(repltask) try Base.REPL_MODULE_REF[] = REPL redirect_stdin(pts) diff --git a/test/core.jl b/test/core.jl index 648dd68602fa5..74df09bcdfd91 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8166,7 +8166,7 @@ let M = @__MODULE__ @test_throws(ErrorException("cannot set type for global $(nameof(M)).a_typed_global. It already has a value or is already set to a different type."), Core.eval(M, :(global a_typed_global::$(Union{Nothing,Tuple{Union{Integer,Nothing}}})))) @test Core.eval(M, :(global a_typed_global)) === nothing - @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} + @test Core.get_binding_type(M, :a_typed_global) == Tuple{Union{Integer,Nothing}} end @test Base.unsafe_convert(Ptr{Int}, [1]) !== C_NULL diff --git a/test/precompile.jl b/test/precompile.jl index 3e8fe44e1b2f0..bc738e557bb51 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1925,7 +1925,7 @@ precompile_test_harness("Issue #50538") do load_path ex isa ErrorException || rethrow() ex end - global undefglobal + global undefglobal::Any end """) ji, ofile = Base.compilecache(Base.PkgId("I50538")) diff --git a/test/syntax.jl b/test/syntax.jl index 0855c643e1423..da69bd98dc010 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -2645,9 +2645,9 @@ end @test_throws ErrorException("invalid method definition in Mod3: function Mod3.f must be explicitly imported to be extended") Core.eval(Mod3, :(f(x::Int) = x)) @test !isdefined(Mod3, :always_undef) # resolve this binding now in Mod3 @test_throws ErrorException("invalid method definition in Mod3: exported function Mod.always_undef does not exist") Core.eval(Mod3, :(always_undef(x::Int) = x)) -@test_throws ErrorException("cannot assign a value to imported variable Mod.always_undef from module Mod3") Core.eval(Mod3, :(const always_undef = 3)) -@test_throws ErrorException("cannot assign a value to imported variable Mod3.f") Core.eval(Mod3, :(const f = 3)) -@test_throws ErrorException("cannot declare Mod.maybe_undef constant; it already has a value") Core.eval(Mod, :(const maybe_undef = 3)) +@test_throws ErrorException("cannot declare Mod3.always_undef constant; it was already declared as an import") Core.eval(Mod3, :(const always_undef = 3)) +@test_throws ErrorException("cannot declare Mod3.f constant; it was already declared as an import") Core.eval(Mod3, :(const f = 3)) +@test_throws ErrorException("cannot declare Mod.maybe_undef constant; it was already declared global") Core.eval(Mod, :(const maybe_undef = 3)) z = 42 import .z as also_z @@ -3704,7 +3704,8 @@ end module Foreign54607 # Syntactic, not dynamic try_to_create_binding1() = (Foreign54607.foo = 2) - @eval try_to_create_binding2() = ($(GlobalRef(Foreign54607, :foo)) = 2) + # GlobalRef is allowed for same-module assignment + @eval try_to_create_binding2() = ($(GlobalRef(Foreign54607, :foo2)) = 2) function global_create_binding() global bar bar = 3 @@ -3719,6 +3720,11 @@ end @test_throws ErrorException (Foreign54607.foo = 1) @test_throws ErrorException Foreign54607.try_to_create_binding1() @test_throws ErrorException Foreign54607.try_to_create_binding2() +function assign_in_foreign_module() + (Foreign54607.foo = 1) + nothing +end +@test !Core.Compiler.is_nothrow(Base.infer_effects(assign_in_foreign_module)) @test_throws ErrorException begin @Base.Experimental.force_compile (Foreign54607.foo = 1) @@ -3904,3 +3910,68 @@ module ExtendedIsDefined @test !$(Expr(:isdefined, GlobalRef(@__MODULE__, :x4), false)) end end + +# Test importing the same module twice using two different paths +module FooDualImport +end +module BarDualImport +import ..FooDualImport +import ..FooDualImport.FooDualImport +end + +# Test trying to define a constant and then importing the same constant +const ImportConstant = 1 +module ImportConstantTestModule + using Test + const ImportConstant = 1 + import ..ImportConstant + @test ImportConstant == 1 + @test isconst(@__MODULE__, :ImportConstant) +end + +# Test trying to define a constant and then trying to assign to the same value +module AssignConstValueTest + const x = 1 + x = 1 +end +@test isconst(AssignConstValueTest, :x) + +# Module Replacement +module ReplacementContainer + module ReplaceMe + const x = 1 + end + const Old = ReplaceMe + module ReplaceMe + const x = 2 + end +end +@test ReplacementContainer.Old !== ReplacementContainer.ReplaceMe +@test ReplacementContainer.ReplaceMe.x === 2 + +# Setglobal of previously declared global +module DeclareSetglobal + using Test + @test_throws ErrorException setglobal!(@__MODULE__, :DeclareMe, 1) + global DeclareMe + setglobal!(@__MODULE__, :DeclareMe, 1) + @test DeclareMe === 1 +end + +# Binding type of const (N.B.: This may change in the future) +module ConstBindingType + using Test + const x = 1 + @test Core.get_binding_type(@__MODULE__, :x) === Any +end + +# Explicit import may resolve using failed +module UsingFailedExplicit + using Test + module A; export x; x = 1; end + module B; export x; x = 2; end + using .A, .B + @test_throws UndefVarError x + using .A: x as x + @test x === 1 +end From e921dc8fb8af13c65b9f5d14fa48b39073741c1a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 29 Aug 2024 14:12:40 -0400 Subject: [PATCH 55/94] Revert "Don't expose guard pages to malloc_stack API consumers" (#55555) Reverts JuliaLang/julia#54591 This cause the runtime to misbehave and crash, since all of the consumers of this information in the runtime assumed that the guard pages are accounted for correctly as part of the reserved allocation. Nothing in the runtime ever promised that it is valid to access the pages beyond the current redzone (indeed, ASAN would forbid it as well). --- src/gc-stacks.c | 47 +++++------------------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/src/gc-stacks.c b/src/gc-stacks.c index 08425019a4daf..783129ea97693 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -23,22 +23,13 @@ // number of stacks to always keep available per pool #define MIN_STACK_MAPPINGS_PER_POOL 5 -#if defined(_OS_WINDOWS_) || (!defined(_OS_OPENBSD_) && !defined(JL_HAVE_UCONTEXT) && !defined(JL_HAVE_SIGALTSTACK)) -#define JL_USE_GUARD_PAGE 1 const size_t jl_guard_size = (4096 * 8); -#else -const size_t jl_guard_size = 0; -#endif - static _Atomic(uint32_t) num_stack_mappings = 0; #ifdef _OS_WINDOWS_ #define MAP_FAILED NULL static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT { - size_t guard_size = LLT_ALIGN(jl_guard_size, jl_page_size); - bufsz += guard_size; - void *stk = VirtualAlloc(NULL, bufsz, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (stk == NULL) return MAP_FAILED; @@ -49,7 +40,6 @@ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT VirtualFree(stk, 0, MEM_RELEASE); return MAP_FAILED; } - stk = (char *)stk + guard_size; jl_atomic_fetch_add_relaxed(&num_stack_mappings, 1); return stk; @@ -58,68 +48,41 @@ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT static void free_stack(void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT { -#ifdef JL_USE_GUARD_PAGE - size_t guard_size = LLT_ALIGN(jl_guard_size, jl_page_size); - bufsz += guard_size; - stkbuf = (char *)stkbuf - guard_size; -#endif - VirtualFree(stkbuf, 0, MEM_RELEASE); jl_atomic_fetch_add_relaxed(&num_stack_mappings, -1); } #else -# ifdef _OS_OPENBSD_ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT { - void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); - if (stk == MAP_FAILED) - return MAP_FAILED; - +# ifdef _OS_OPENBSD_ // we don't set up a guard page to detect stack overflow: on OpenBSD, any // mmap-ed region has guard page managed by the kernel, so there is no // need for it. Additionally, a memory region used as stack (memory // allocated with MAP_STACK option) has strict permission, and you can't // "create" a guard page on such memory by using `mprotect` on it - - jl_atomic_fetch_add_relaxed(&num_stack_mappings, 1); - return stk; -} + void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stk == MAP_FAILED) + return MAP_FAILED; # else -static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT -{ -#ifdef JL_USE_GUARD_PAGE - size_t guard_size = LLT_ALIGN(jl_guard_size, jl_page_size); - bufsz += guard_size; -#endif - void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (stk == MAP_FAILED) return MAP_FAILED; -#ifdef JL_USE_GUARD_PAGE // set up a guard page to detect stack overflow if (mprotect(stk, jl_guard_size, PROT_NONE) == -1) { munmap(stk, bufsz); return MAP_FAILED; } - stk = (char *)stk + guard_size; -#endif +# endif jl_atomic_fetch_add_relaxed(&num_stack_mappings, 1); return stk; } -# endif static void free_stack(void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT { -#ifdef JL_USE_GUARD_PAGE - size_t guard_size = LLT_ALIGN(jl_guard_size, jl_page_size); - bufsz += guard_size; - stkbuf = (char *)stkbuf - guard_size; -#endif - munmap(stkbuf, bufsz); jl_atomic_fetch_add_relaxed(&num_stack_mappings, -1); } From da3468c1208b087161af5b69a26a92a91967a367 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 29 Aug 2024 14:45:01 -0400 Subject: [PATCH 56/94] add pending state back to jl_thread_suspend_and_get_state-machine (#55622) Fixes an issue with #55500, where signals may abruptly abort the process as they observe it is still processing the resume SIGUSR2 message and are not able to wait for that processing to end before setting the new message to exit. --- src/signals-unix.c | 65 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/src/signals-unix.c b/src/signals-unix.c index 005422bea03d3..d719ac7fa452d 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -448,6 +448,7 @@ static int signal_caught_cond = -1; int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) { + int err; pthread_mutex_lock(&in_signal_lock); jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; @@ -456,22 +457,51 @@ int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) pthread_mutex_unlock(&in_signal_lock); return 0; } - sig_atomic_t request = 0; - if (!jl_atomic_cmpswap(&ptls2->signal_request, &request, 1)) { + if (jl_atomic_load(&ptls2->signal_request) != 0) { // something is wrong, or there is already a usr2 in flight elsewhere + // try to wait for it to finish or wait for timeout + struct pollfd event = {signal_caught_cond, POLLIN, 0}; + do { + err = poll(&event, 1, timeout * 1000); + } while (err == -1 && errno == EINTR); + if (err == -1 || (event.revents & POLLIN) == 0) { + // not ready after timeout: cancel this request + pthread_mutex_unlock(&in_signal_lock); + return 0; + } + } + // check for any stale signal_caught_cond events + struct pollfd event = {signal_caught_cond, POLLIN, 0}; + do { + err = poll(&event, 1, 0); + } while (err == -1 && errno == EINTR); + if (err == -1) { pthread_mutex_unlock(&in_signal_lock); return 0; } + if ((event.revents & POLLIN) != 0) { + // consume it before continuing + eventfd_t got; + do { + err = read(signal_caught_cond, &got, sizeof(eventfd_t)); + } while (err == -1 && errno == EINTR); + if (err != sizeof(eventfd_t)) abort(); + assert(got == 1); (void) got; + } + sig_atomic_t request = jl_atomic_exchange(&ptls2->signal_request, 1); + assert(request == 0 || request == -1); request = 1; - int err = pthread_kill(ptls2->system_id, SIGUSR2); - // wait for thread to acknowledge or timeout - struct pollfd event = {signal_caught_cond, POLLIN, 0}; + err = pthread_kill(ptls2->system_id, SIGUSR2); if (err == 0) { + // wait for thread to acknowledge or timeout + struct pollfd event = {signal_caught_cond, POLLIN, 0}; do { err = poll(&event, 1, timeout * 1000); } while (err == -1 && errno == EINTR); + if (err != 1 || (event.revents & POLLIN) == 0) + err = -1; } - if ((event.revents & POLLIN) == 0) { + if (err == -1) { // not ready after timeout: try to cancel this request if (jl_atomic_cmpswap(&ptls2->signal_request, &request, 0)) { pthread_mutex_unlock(&in_signal_lock); @@ -487,7 +517,7 @@ int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) // Now the other thread is waiting on exit_signal_cond (verify that here by // checking it is 0, and add an acquire barrier for good measure) request = jl_atomic_load_acquire(&ptls2->signal_request); - assert(request == 0); (void) request; + assert(request == 0 || request == -1); (void) request; jl_atomic_store_release(&ptls2->signal_request, 4); // prepare to resume normally, but later code may change this *ctx = *signal_context; return 1; @@ -546,6 +576,7 @@ static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) } // request: +// -1: processing // 0: nothing [not from here] // 1: get state & wait for request // 2: throw sigint if `!defer_signal && io_wait` or if force throw threshold @@ -561,22 +592,36 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) if (ptls == NULL) return; int errno_save = errno; - // acknowledge that we saw the signal_request - sig_atomic_t request = jl_atomic_exchange(&ptls->signal_request, 0); + sig_atomic_t request = jl_atomic_load(&ptls->signal_request); + if (request == 0) + return; + if (!jl_atomic_cmpswap(&ptls->signal_request, &request, -1)) + return; if (request == 1) { signal_context = jl_to_bt_context(ctx); + // acknowledge that we saw the signal_request and set signal_context int err; eventfd_t got = 1; err = write(signal_caught_cond, &got, sizeof(eventfd_t)); if (err != sizeof(eventfd_t)) abort(); + sig_atomic_t processing = -1; + jl_atomic_cmpswap(&ptls->signal_request, &processing, 0); + // wait for exit signal do { err = read(exit_signal_cond, &got, sizeof(eventfd_t)); } while (err == -1 && errno == EINTR); if (err != sizeof(eventfd_t)) abort(); assert(got == 1); - request = jl_atomic_exchange(&ptls->signal_request, 0); + request = jl_atomic_exchange(&ptls->signal_request, -1); + signal_context = NULL; assert(request == 2 || request == 3 || request == 4); } + int err; + eventfd_t got = 1; + err = write(signal_caught_cond, &got, sizeof(eventfd_t)); + if (err != sizeof(eventfd_t)) abort(); + sig_atomic_t processing = -1; + jl_atomic_cmpswap(&ptls->signal_request, &processing, 0); if (request == 2) { int force = jl_check_force_sigint(); if (force || (!ptls->defer_signal && ptls->io_wait)) { From fdc109088e7a25c7b412546634f2db70a2d584ad Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Fri, 30 Aug 2024 08:45:42 +0530 Subject: [PATCH 57/94] Specialize newindex for StructuredMatrix broadcasting (#55626) This provides most of the benefits seen in https://github.com/JuliaLang/julia/pull/55604. The simpler implementation appears to help with branch-prediction in inferring the zero elements of the structured matrices. The improved performance as a consequence: ```julia julia> using LinearAlgebra julia> U = UpperTriangular(rand(3000,3000)); D = Diagonal(rand(size(U,1))); julia> @btime $U .+ $D; 23.405 ms (3 allocations: 68.66 MiB) # nightly 15.266 ms (3 allocations: 68.66 MiB) # This PR ``` --------- Co-authored-by: Matt Bauman --- stdlib/LinearAlgebra/src/structuredbroadcast.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/LinearAlgebra/src/structuredbroadcast.jl b/stdlib/LinearAlgebra/src/structuredbroadcast.jl index 21f6a7414d872..3c60b37959f91 100644 --- a/stdlib/LinearAlgebra/src/structuredbroadcast.jl +++ b/stdlib/LinearAlgebra/src/structuredbroadcast.jl @@ -199,6 +199,8 @@ function Broadcast.newindex(A::StructuredMatrix, b::BandIndex) # and we apply newindex to both the axes at once to obtain the result size(A,1) > 1 ? b : BandIndex(0, 1) end +# All structured matrices are square, and therefore they only broadcast out if they are size (1, 1) +Broadcast.newindex(D::StructuredMatrix, I::CartesianIndex{2}) = size(D) == (1,1) ? CartesianIndex(1,1) : I function copyto!(dest::Diagonal, bc::Broadcasted{<:StructuredMatrixStyle}) isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) From ed084578bf171e223838dd62e91cec315ab2c29a Mon Sep 17 00:00:00 2001 From: James Wrigley Date: Fri, 30 Aug 2024 14:23:08 +0200 Subject: [PATCH 58/94] Document how to set the threadpool sizes with `JULIA_NUM_THREADS` (#55425) Fixes #48960, fixes #50936. --- doc/src/manual/environment-variables.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/doc/src/manual/environment-variables.md b/doc/src/manual/environment-variables.md index 30f2263904f40..1fb11018a22e7 100644 --- a/doc/src/manual/environment-variables.md +++ b/doc/src/manual/environment-variables.md @@ -328,16 +328,25 @@ a master process to establish a connection before dying. ### [`JULIA_NUM_THREADS`](@id JULIA_NUM_THREADS) -An unsigned 64-bit integer (`uint64_t`) that sets the maximum number of threads -available to Julia. If `$JULIA_NUM_THREADS` is not positive or is not set, or -if the number of CPU threads cannot be determined through system calls, then the -number of threads is set to `1`. +An unsigned 64-bit integer (`uint64_t`) or string that sets the maximum number +of threads available to Julia. If `$JULIA_NUM_THREADS` is not set or is a +non-positive integer, or if the number of CPU threads cannot be determined +through system calls, then the number of threads is set to `1`. If `$JULIA_NUM_THREADS` is set to `auto`, then the number of threads will be set -to the number of CPU threads. +to the number of CPU threads. It can also be set to a comma-separated string to +specify the size of the `:default` and `:interactive` [threadpools](@ref +man-threadpools), respectively: +```bash +# 5 threads in the :default pool and 2 in the :interactive pool +export JULIA_NUM_THREADS=5,2 + +# `auto` threads in the :default pool and 1 in the :interactive pool +export JULIA_NUM_THREADS=auto,1 +``` !!! note - `JULIA_NUM_THREADS` must be defined before starting julia; defining it in + `JULIA_NUM_THREADS` must be defined before starting Julia; defining it in `startup.jl` is too late in the startup process. !!! compat "Julia 1.5" @@ -347,6 +356,9 @@ to the number of CPU threads. !!! compat "Julia 1.7" The `auto` value for `$JULIA_NUM_THREADS` requires Julia 1.7 or above. +!!! compat "Julia 1.9" + The `x,y` format for threadpools requires Julia 1.9 or above. + ### [`JULIA_THREAD_SLEEP_THRESHOLD`](@id JULIA_THREAD_SLEEP_THRESHOLD) If set to a string that starts with the case-insensitive substring `"infinite"`, From 1e21727943915bba7701c668d89aece8a9bdcccb Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 30 Aug 2024 15:37:00 +0200 Subject: [PATCH 59/94] Homogenize the progress bar in Pkg and base package precompilation (#55588) - Fix a missing check to `always_reprint` that has been fixed in Pkg - The header was in `light_green` while in Pkg it was `green`. - The progress bar for downloading packages and artifacts was in green while compiling package was in blue. - The progress bar is now attached to the "Precompiling packages" header like for artifact and package downloads. Co-authored-by: KristofferC --- base/precompilation.jl | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/base/precompilation.jl b/base/precompilation.jl index 6997ce12c8d01..32fdb86bbdb07 100644 --- a/base/precompilation.jl +++ b/base/precompilation.jl @@ -285,7 +285,7 @@ function show_progress(io::IO, p::MiniProgressBar; termwidth=nothing, carriagere return end t = time() - if p.has_shown && (t - p.time_shown) < PROGRESS_BAR_TIME_GRANULARITY[] + if !p.always_reprint && p.has_shown && (t - p.time_shown) < PROGRESS_BAR_TIME_GRANULARITY[] return end p.time_shown = t @@ -301,9 +301,11 @@ function show_progress(io::IO, p::MiniProgressBar; termwidth=nothing, carriagere max_progress_width = max(0, min(termwidth - textwidth(p.header) - textwidth(progress_text) - 10 , p.width)) n_filled = ceil(Int, max_progress_width * perc / 100) n_left = max_progress_width - n_filled + headers = split(p.header, ' ') to_print = sprint(; context=io) do io print(io, " "^p.indent) - printstyled(io, p.header, color=p.color, bold=true) + printstyled(io, headers[1], " "; color=:green, bold=true) + printstyled(io, join(headers[2:end], ' ')) print(io, " ") printstyled(io, "━"^n_filled; color=p.color) printstyled(io, perc >= 95 ? "━" : "╸"; color=p.color) @@ -343,7 +345,7 @@ import Base: StaleCacheKey can_fancyprint(io::IO) = io isa Base.TTY && (get(ENV, "CI", nothing) != "true") -function printpkgstyle(io, header, msg; color=:light_green) +function printpkgstyle(io, header, msg; color=:green) printstyled(io, header; color, bold=true) println(io, " ", msg) end @@ -564,9 +566,6 @@ function precompilepkgs(pkgs::Vector{String}=String[]; if !manifest if isempty(pkgs) pkgs = [pkg.name for pkg in direct_deps] - target = "all packages" - else - target = join(pkgs, ", ") end # restrict to dependencies of given packages function collect_all_deps(depsmap, dep, alldeps=Set{Base.PkgId}()) @@ -602,18 +601,16 @@ function precompilepkgs(pkgs::Vector{String}=String[]; return end end - else - target = "manifest" end nconfigs = length(configs) + target = nothing if nconfigs == 1 if !isempty(only(configs)[1]) - target *= " for configuration $(join(only(configs)[1], " "))" + target = "for configuration $(join(only(configs)[1], " "))" end - target *= "..." else - target *= " for $nconfigs compilation configurations..." + target = "for $nconfigs compilation configurations..." end @debug "precompile: packages filtered" @@ -695,15 +692,19 @@ function precompilepkgs(pkgs::Vector{String}=String[]; try wait(first_started) (isempty(pkg_queue) || interrupted_or_done.set) && return - fancyprint && lock(print_lock) do - printpkgstyle(io, :Precompiling, target) - print(io, ansi_disablecursor) + lock(print_lock) do + if target !== nothing + printpkgstyle(io, :Precompiling, target) + end + if fancyprint + print(io, ansi_disablecursor) + end end t = Timer(0; interval=1/10) anim_chars = ["◐","◓","◑","◒"] i = 1 last_length = 0 - bar = MiniProgressBar(; indent=2, header = "Progress", color = Base.info_color(), percentage=false, always_reprint=true) + bar = MiniProgressBar(; indent=0, header = "Precompiling packages ", color = :green, percentage=false, always_reprint=true) n_total = length(depsmap) * length(configs) bar.max = n_total - n_already_precomp final_loop = false @@ -832,8 +833,10 @@ function precompilepkgs(pkgs::Vector{String}=String[]; config_str = "$(join(flags, " "))" name *= color_string(" $(config_str)", :light_black) end - !fancyprint && lock(print_lock) do - isempty(pkg_queue) && printpkgstyle(io, :Precompiling, target) + lock(print_lock) do + if !fancyprint && target === nothing && isempty(pkg_queue) + printpkgstyle(io, :Precompiling, "packages...") + end end push!(pkg_queue, pkg_config) started[pkg_config] = true From b6d2155e65bbbf511a78aadf8e21fcec51e525c4 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:38:36 +0200 Subject: [PATCH 60/94] add `show` methods for `ReentrantLock`, `Condition`, and `GenericCondition` (#55239) Master: ```julia julia> Condition() Condition(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.AlwaysLockedST(1)) julia> ReentrantLock() ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{Base.Threads.SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), (34, 0, -1)) julia> Base.Semaphore(1) Base.Semaphore(1, 0, Base.GenericCondition{ReentrantLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), ReentrantLock(nothing, 0x00000000, 0x00, Base.GenericCondition{Base.Threads.SpinLock}(Base.IntrusiveLinkedList{Task}(nothing, nothing), Base.Threads.SpinLock(0)), (4613101808, 4613101840, -1)))) ``` These are pretty noisy, and adds clutter to the default `show` for any object that includes them. PR: ```julia julia> Condition() Condition() julia> ReentrantLock() ReentrantLock() (unlocked) julia> Base.Semaphore(1) Base.Semaphore(1, 0, Base.GenericCondition(ReentrantLock())) ``` Here I haven't defined a custom `show` for `Base.Semaphore`, but we can see it's printing is much cleaner thanks to the improved printing of its fields. For `ReentrantLock`, it could be potentially interesting to surface some of the fields (like `locked_by` and `havelock`) still but I'm not sure anyone looks at them via `show`, and I'm not sure if there's a good way to print it compactly. --------- Co-authored-by: Jameson Nash --- base/condition.jl | 4 ++++ base/lock.jl | 14 ++++++++++++++ test/channels.jl | 3 +++ test/copy.jl | 2 ++ test/misc.jl | 10 ++++++++++ 5 files changed, 33 insertions(+) diff --git a/base/condition.jl b/base/condition.jl index bc14b17b3ac6b..fd771c9be346a 100644 --- a/base/condition.jl +++ b/base/condition.jl @@ -69,6 +69,8 @@ struct GenericCondition{L<:AbstractLock} GenericCondition(l::AbstractLock) = new{typeof(l)}(IntrusiveLinkedList{Task}(), l) end +show(io::IO, c::GenericCondition) = print(io, GenericCondition, "(", c.lock, ")") + assert_havelock(c::GenericCondition) = assert_havelock(c.lock) lock(c::GenericCondition) = lock(c.lock) unlock(c::GenericCondition) = unlock(c.lock) @@ -194,6 +196,8 @@ This object is NOT thread-safe. See [`Threads.Condition`](@ref) for a thread-saf """ const Condition = GenericCondition{AlwaysLockedST} +show(io::IO, ::Condition) = print(io, Condition, "()") + lock(c::GenericCondition{AlwaysLockedST}) = throw(ArgumentError("`Condition` is not thread-safe. Please use `Threads.Condition` instead for multi-threaded code.")) unlock(c::GenericCondition{AlwaysLockedST}) = diff --git a/base/lock.jl b/base/lock.jl index b69f3c5c03638..b473045e5809d 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -51,6 +51,20 @@ end assert_havelock(l::ReentrantLock) = assert_havelock(l, l.locked_by) +show(io::IO, ::ReentrantLock) = print(io, ReentrantLock, "()") + +function show(io::IO, ::MIME"text/plain", l::ReentrantLock) + show(io, l) + if !(get(io, :compact, false)::Bool) + locked_by = l.locked_by + if locked_by isa Task + print(io, " (locked by ", locked_by === current_task() ? "current " : "", locked_by, ")") + else + print(io, " (unlocked)") + end + end +end + """ islocked(lock) -> Status (Boolean) diff --git a/test/channels.jl b/test/channels.jl index d62c0b581775c..eed7a7ecc0566 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -12,6 +12,9 @@ using Base: n_avail end @test wait(a) == "success" @test fetch(t) == "finished" + + # Test printing + @test repr(a) == "Condition()" end @testset "wait first behavior of wait on Condition" begin diff --git a/test/copy.jl b/test/copy.jl index d2f555604c4d8..559bf5d3e757a 100644 --- a/test/copy.jl +++ b/test/copy.jl @@ -282,6 +282,8 @@ end @testset "`deepcopy` a `GenericCondition`" begin a = Base.GenericCondition(ReentrantLock()) + # Test printing + @test repr(a) == "Base.GenericCondition(ReentrantLock())" @test !islocked(a.lock) lock(a.lock) @test islocked(a.lock) diff --git a/test/misc.jl b/test/misc.jl index 87605d685fb3e..66b70956935cd 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -159,6 +159,16 @@ let @test @lock(lockable2, lockable2[]["foo"]) == "hello" end +@testset "`show` for ReentrantLock" begin + l = ReentrantLock() + @test repr(l) == "ReentrantLock()" + @test repr("text/plain", l) == "ReentrantLock() (unlocked)" + @lock l begin + @test startswith(repr("text/plain", l), "ReentrantLock() (locked by current Task (") + end + @test repr("text/plain", l) == "ReentrantLock() (unlocked)" +end + for l in (Threads.SpinLock(), ReentrantLock()) @test get_finalizers_inhibited() == 0 @test lock(get_finalizers_inhibited, l) == 1 From 4c2f728a9976a5651acfe2f7eba703e6d0b64562 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Fri, 30 Aug 2024 10:29:56 -0700 Subject: [PATCH 61/94] Call `pthread_attr_init` in `jl_init_stack_limits` (#55594) PR 55515 seems to have introduced segfaults on FreeBSD during the `atexit` and `ccall` tests. Prior to that PR, we had been calling `pthread_attr_init` in `jl_init_stack_limits` on FreeBSD. According to the [manual page](https://man.freebsd.org/cgi/man.cgi?query=pthread_attr_get_np&apropos=0&sektion=3&manpath=FreeBSD+13.2-RELEASE&arch=default&format=html) for `pthread_attr_get_np`, it is "HIGHLY RECOMMENDED" to use `pthread_attr_init` to allocate storage. Might as well put it back then, as it fixes the segfaults. --- src/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/init.c b/src/init.c index 1d466a0a736f9..86c0877b14289 100644 --- a/src/init.c +++ b/src/init.c @@ -67,6 +67,7 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) # if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) pthread_attr_t attr; #if defined(_OS_FREEBSD_) + pthread_attr_init(&attr); pthread_attr_get_np(pthread_self(), &attr); #else pthread_getattr_np(pthread_self(), &attr); From e22e4de5de786778b48650f2929fc6f2172d5b81 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 30 Aug 2024 22:18:25 -0400 Subject: [PATCH 62/94] Fix an assertion in new binding code (#55636) This snuck by me late in the new bindings PR and I didn't notice the assert builder handn't finished yet. --- src/module.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/module.c b/src/module.c index 7f03fc7e66a30..96d94049cff13 100644 --- a/src/module.c +++ b/src/module.c @@ -384,7 +384,7 @@ static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROO static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_t world) { jl_ptr_kind_union_t owner_pku = jl_atomic_load_relaxed(&owner->restriction); - assert(decode_restriction_kind(owner_pku) == BINDING_KIND_GLOBAL || + assert(decode_restriction_kind(owner_pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(owner_pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(owner_pku))); jl_binding_partition_t *alias_bpart = jl_get_binding_partition(alias, world); if (owner == alias_bpart) @@ -419,7 +419,7 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl continue; jl_binding_partition_t *tempbpart = jl_get_binding_partition(tempb, jl_current_task->world_age); jl_ptr_kind_union_t tempb_pku = jl_atomic_load_relaxed(&tempbpart->restriction); - assert(decode_restriction_kind(tempb_pku) == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(decode_restriction_kind(tempb_pku))); + 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))); (void)tempb_pku; if (bpart != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempbpart, b, jl_current_task->world_age)) { if (warn) { @@ -663,7 +663,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, else { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - assert(decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(decode_restriction_kind(pku))); + assert(decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(pku))); (void)pku; if (b->deprecated) { if (jl_get_binding_value(b) == jl_nothing) { From 0622123121a33668f4dc771a6183b95fff533a53 Mon Sep 17 00:00:00 2001 From: Benjamin Lorenz Date: Sat, 31 Aug 2024 13:37:48 +0200 Subject: [PATCH 63/94] fixes/cleanup for use-system-unwind make flags (#55639) Assuming non-windows and libunwind not disabled: The flag `-DLLVMLIBUNWIND` is currently set on macos only for `USE_SYSTEM_UNWIND=0` which seems wrong to me and causes build issues for macos on Yggdrasil in combination with the recent https://github.com/JuliaLang/julia/pull/55049 which should only affect gnu libunwind (`error: call to undeclared function 'unw_ensure_tls'`). This flag is now set independently of the system-libunwind flag (on Darwin and OpenBSD as before). `LIBUNWIND=-lunwind` is set for `USE_SYSTEM_UNWIND=0` || `USE_SYSTEM_UNWIND=1` && `OS != Darwin`. I don't think the check for Darwin make sense and might be a leftover from using osxunwind a (long) while ago. Changed that to always set `-lunwind` if enabled. x-ref: https://github.com/JuliaPackaging/Yggdrasil/pull/9331 --- Make.inc | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Make.inc b/Make.inc index 0da638cfab52e..f078a0c84f806 100644 --- a/Make.inc +++ b/Make.inc @@ -1095,20 +1095,13 @@ LIBUNWIND:= else ifneq ($(DISABLE_LIBUNWIND), 0) LIBUNWIND:= else -ifeq ($(USE_SYSTEM_LIBUNWIND), 1) -ifneq ($(OS),Darwin) LIBUNWIND:=-lunwind -# Only for linux since we want to use not yet released libunwind features -JCFLAGS+=-DSYSTEM_LIBUNWIND -JCPPFLAGS+=-DSYSTEM_LIBUNWIND -endif -else ifneq ($(findstring $(OS),Darwin OpenBSD),) -LIBUNWIND:=-lunwind JCPPFLAGS+=-DLLVMLIBUNWIND -else -LIBUNWIND:=-lunwind -endif +else ifeq ($(USE_SYSTEM_LIBUNWIND), 1) +# Only for linux and freebsd since we want to use not yet released gnu libunwind features +JCFLAGS+=-DSYSTEM_LIBUNWIND +JCPPFLAGS+=-DSYSTEM_LIBUNWIND endif endif From ca72e28f26b7e2fb617ce8f83fd82ade62007e07 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Sat, 31 Aug 2024 13:47:48 +0200 Subject: [PATCH 64/94] allow `-m` to run the entry point in a submodule (#55265) --- base/client.jl | 4 ++-- test/loading.jl | 1 + test/project/Rot13/src/Rot13.jl | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index 0290d27b09cf0..2ca88c40aeb7e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -292,12 +292,12 @@ function exec_options(opts) invokelatest(show, Core.eval(Main, parse_input_line(arg))) println() elseif cmd == 'm' - @eval Main import $(Symbol(arg)).main + entrypoint = push!(split(arg, "."), "main") + Base.eval(Main, Expr(:import, Expr(:., Symbol.(entrypoint)...))) if !should_use_main_entrypoint() error("`main` in `$arg` not declared as entry point (use `@main` to do so)") end return false - elseif cmd == 'L' # load file immediately on all processors if !distributed_mode diff --git a/test/loading.jl b/test/loading.jl index 51e0c45d2faf1..fe6f800276547 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1584,6 +1584,7 @@ end @testset "-m" begin rot13proj = joinpath(@__DIR__, "project", "Rot13") @test readchomp(`$(Base.julia_cmd()) --startup-file=no --project=$rot13proj -m Rot13 --project nowhere ABJURER`) == "--cebwrpg abjurer NOWHERE " + @test readchomp(`$(Base.julia_cmd()) --startup-file=no --project=$rot13proj -m Rot13.Rot26 --project nowhere ABJURER`) == "--project nowhere ABJURER " end @testset "workspace loading" begin diff --git a/test/project/Rot13/src/Rot13.jl b/test/project/Rot13/src/Rot13.jl index 1d19cbbe6df91..66f077812d878 100644 --- a/test/project/Rot13/src/Rot13.jl +++ b/test/project/Rot13/src/Rot13.jl @@ -12,4 +12,17 @@ function (@main)(args) return 0 end +module Rot26 # LOL + +import ..rot13 + +rot26(str::AbstractString) = map(rot13 ∘ rot13, str) + +function (@main)(args) + foreach(arg -> print(rot26(arg), " "), args) + return 0 +end + +end + end # module Rot13 From 42d00107b0b3eb1a77dbefc1aea77c53480bf259 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerberg <39104088+nhz2@users.noreply.github.com> Date: Sat, 31 Aug 2024 09:16:47 -0400 Subject: [PATCH 65/94] Document and test `Base.rename` (#55503) Follow up of #55384 --- base/file.jl | 17 ++- test/file.jl | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 314 insertions(+), 5 deletions(-) diff --git a/base/file.jl b/base/file.jl index 81bca9dd65577..d45f34fb55dc6 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1183,15 +1183,24 @@ function unlink(p::AbstractString) end """ - rename(oldpath::AbstractString, newpath::AbstractString) + Base.rename(oldpath::AbstractString, newpath::AbstractString) -Change the name of a file from `oldpath` to `newpath`. If `newpath` is an existing file it may be replaced. -Equivalent to [rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html). -Throws an `IOError` on failure. +Change the name of a file or directory from `oldpath` to `newpath`. +If `newpath` is an existing file or empty directory it may be replaced. +Equivalent to [rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html) on Unix. +If a path contains a "\\0" throw an `ArgumentError`. +On other failures throw an `IOError`. Return `newpath`. OS-specific restrictions may apply when `oldpath` and `newpath` are in different directories. +Currently there are a few differences in behavior on Windows which may be resolved in a future release. +Specifically, currently on Windows: +1. `rename` will fail if `oldpath` or `newpath` are opened files. +2. `rename` will fail if `newpath` is an existing directory. +3. `rename` may work if `newpath` is a file and `oldpath` is a directory. +4. `rename` may remove `oldpath` if it is a hardlink to `newpath`. + See also: [`mv`](@ref). """ function rename(oldpath::AbstractString, newpath::AbstractString) diff --git a/test/file.jl b/test/file.jl index 4531cd8e66998..de6d488056a02 100644 --- a/test/file.jl +++ b/test/file.jl @@ -823,6 +823,303 @@ mktempdir() do tmpdir rm(b_tmpdir) end +@testset "rename" begin + # some of the windows specific behavior may be fixed in new versions of julia + mktempdir() do dir + # see if can make symlinks + local can_symlink = try + symlink("foo", joinpath(dir, "link")) + rm(joinpath(dir, "link")) + true + catch + false + end + local f1 = joinpath(dir, "file1") + local f2 = joinpath(dir, "file2") + local d1 = joinpath(dir, "dir1") + local d2 = joinpath(dir, "dir2") + local subd1f1 = joinpath(d1, "file1") + local subd1f2 = joinpath(d1, "file2") + local subd2f1 = joinpath(d2, "file1") + local subd2f2 = joinpath(d2, "file2") + local h1 = joinpath(dir, "hlink1") + local h2 = joinpath(dir, "hlink2") + local s1 = joinpath(dir, "slink1") + local s2 = joinpath(dir, "slink2") + @testset "renaming to non existing newpath in same directory" begin + # file, make sure isexecutable is copied + for mode in (0o644, 0o755) + write(f1, b"data") + chmod(f1, mode) + Base.rename(f1, f2) + @test !isfile(f1) + @test isfile(f2) + @test read(f2) == b"data" + if mode == 0o644 + @test !isexecutable(f2) + else + @test isexecutable(f2) + end + rm(f2) + end + # empty directory + mkdir(d1) + Base.rename(d1, d2) + @test !isdir(d1) + @test isdir(d2) + @test isempty(readdir(d2)) + rm(d2) + # non empty directory + mkdir(d1) + write(subd1f1, b"data") + chmod(subd1f1, 0o644) + write(subd1f2, b"exe") + chmod(subd1f2, 0o755) + Base.rename(d1, d2) + @test !isdir(d1) + @test isdir(d2) + @test read(subd2f1) == b"data" + @test read(subd2f2) == b"exe" + @test !isexecutable(subd2f1) + @test isexecutable(subd2f2) + rm(d2; recursive=true) + # hardlink + write(f1, b"data") + hardlink(f1, h1) + Base.rename(h1, h2) + @test isfile(f1) + @test !isfile(h1) + @test isfile(h2) + @test read(h2) == b"data" + write(h2, b"data2") + @test read(f1) == b"data2" + rm(h2) + rm(f1) + # symlink + if can_symlink + symlink("foo", s1) + Base.rename(s1, s2) + @test !islink(s1) + @test islink(s2) + @test readlink(s2) == "foo" + rm(s2) + end + end + @test isempty(readdir(dir)) # make sure everything got cleaned up + + # Get the error code from failed rename, or nothing if it worked + function rename_errorcodes(oldpath, newpath) + try + Base.rename(oldpath, newpath) + nothing + catch e + e.code + end + end + @testset "errors" begin + # invalid paths + @test_throws ArgumentError Base.rename(f1*"\0", "") + @test Base.UV_ENOENT == rename_errorcodes("", "") + write(f1, b"data") + @test Base.UV_ENOENT == rename_errorcodes(f1, "") + @test read(f1) == b"data" + @test Base.UV_ENOENT == rename_errorcodes("", f1) + @test read(f1) == b"data" + @test Base.UV_ENOENT == rename_errorcodes(f2, f1) + @test read(f1) == b"data" + @test Base.UV_ENOENT == rename_errorcodes(f1, subd1f1) + @test read(f1) == b"data" + rm(f1) + # attempt to make a directory a subdirectory of itself + mkdir(d1) + if Sys.iswindows() + @test rename_errorcodes(d1, joinpath(d1, "subdir")) ∈ (Base.UV_EINVAL, Base.UV_EBUSY) + else + @test Base.UV_EINVAL == rename_errorcodes(d1, joinpath(d1, "subdir")) + end + rm(d1) + # rename to child of a file + mkdir(d1) + write(f2, "foo") + if Sys.iswindows() + @test Base.UV_EINVAL == rename_errorcodes(d1, joinpath(f2, "subdir")) + else + @test Base.UV_ENOTDIR == rename_errorcodes(d1, joinpath(f2, "subdir")) + end + # replace a file with a directory + if !Sys.iswindows() + @test Base.UV_ENOTDIR == rename_errorcodes(d1, f2) + else + # this should work on windows + Base.rename(d1, f2) + @test isdir(f2) + @test !ispath(d1) + end + rm(f2; force=true) + rm(d1; force=true) + # symlink loop + if can_symlink + symlink(s1, s2) + symlink(s2, s1) + @test Base.UV_ELOOP == rename_errorcodes(joinpath(s1, "foo"), f2) + write(f2, b"data") + @test Base.UV_ELOOP == rename_errorcodes(f2, joinpath(s1, "foo")) + rm(s1) + rm(s2) + rm(f2) + end + # newpath is a nonempty directory + mkdir(d1) + mkdir(d2) + write(subd2f1, b"data") + write(f1, b"otherdata") + if Sys.iswindows() + @test Base.UV_EACCES == rename_errorcodes(f1, d1) + @test Base.UV_EACCES == rename_errorcodes(f1, d2) + @test Base.UV_EACCES == rename_errorcodes(d1, d2) + @test Base.UV_EACCES == rename_errorcodes(subd2f1, d2) + else + @test Base.UV_EISDIR == rename_errorcodes(f1, d1) + @test Base.UV_EISDIR == rename_errorcodes(f1, d2) + @test rename_errorcodes(d1, d2) ∈ (Base.UV_ENOTEMPTY, Base.UV_EEXIST) + @test rename_errorcodes(subd2f1, d2) ∈ (Base.UV_ENOTEMPTY, Base.UV_EEXIST, Base.UV_EISDIR) + end + rm(f1) + rm(d1) + rm(d2; recursive=true) + end + @test isempty(readdir(dir)) # make sure everything got cleaned up + + @testset "replacing existing file" begin + write(f2, b"olddata") + chmod(f2, 0o755) + write(f1, b"newdata") + chmod(f1, 0o644) + @test isexecutable(f2) + @test !isexecutable(f1) + Base.rename(f1, f2) + @test !ispath(f1) + @test read(f2) == b"newdata" + @test !isexecutable(f2) + rm(f2) + end + + @testset "replacing file with itself" begin + write(f1, b"data") + Base.rename(f1, f1) + @test read(f1) == b"data" + hardlink(f1, h1) + Base.rename(f1, h1) + if Sys.iswindows() + # On Windows f1 gets deleted + @test !ispath(f1) + else + @test read(f1) == b"data" + end + @test read(h1) == b"data" + rm(h1) + rm(f1; force=true) + end + + @testset "replacing existing file in different directories" begin + mkdir(d1) + mkdir(d2) + write(subd2f2, b"olddata") + chmod(subd2f2, 0o755) + write(subd1f1, b"newdata") + chmod(subd1f1, 0o644) + @test isexecutable(subd2f2) + @test !isexecutable(subd1f1) + Base.rename(subd1f1, subd2f2) + @test !ispath(subd1f1) + @test read(subd2f2) == b"newdata" + @test !isexecutable(subd2f2) + @test isdir(d1) + @test isdir(d2) + rm(d1; recursive=true) + rm(d2; recursive=true) + end + + @testset "rename with open files" begin + # both open + write(f2, b"olddata") + write(f1, b"newdata") + open(f1) do handle1 + open(f2) do handle2 + if Sys.iswindows() + # currently this doesn't work on windows + @test Base.UV_EBUSY == rename_errorcodes(f1, f2) + else + Base.rename(f1, f2) + @test !ispath(f1) + @test read(f2) == b"newdata" + end + # rename doesn't break already opened files + @test read(handle1) == b"newdata" + @test read(handle2) == b"olddata" + end + end + rm(f1; force=true) + rm(f2; force=true) + + # oldpath open + write(f2, b"olddata") + write(f1, b"newdata") + open(f1) do handle1 + if Sys.iswindows() + # currently this doesn't work on windows + @test Base.UV_EBUSY == rename_errorcodes(f1, f2) + else + Base.rename(f1, f2) + @test !ispath(f1) + @test read(f2) == b"newdata" + end + # rename doesn't break already opened files + @test read(handle1) == b"newdata" + end + rm(f1; force=true) + rm(f2; force=true) + + # newpath open + write(f2, b"olddata") + write(f1, b"newdata") + open(f2) do handle2 + if Sys.iswindows() + # currently this doesn't work on windows + @test Base.UV_EACCES == rename_errorcodes(f1, f2) + else + Base.rename(f1, f2) + @test !ispath(f1) + @test read(f2) == b"newdata" + end + # rename doesn't break already opened files + @test read(handle2) == b"olddata" + end + rm(f1; force=true) + rm(f2; force=true) + end + + @testset "replacing empty directory with directory" begin + mkdir(d1) + mkdir(d2) + write(subd1f1, b"data") + if Sys.iswindows() + # currently this doesn't work on windows + @test Base.UV_EACCES == rename_errorcodes(d1, d2) + rm(d1; recursive=true) + rm(d2) + else + Base.rename(d1, d2) + @test isdir(d2) + @test read(subd2f1) == b"data" + @test !ispath(d1) + rm(d2; recursive=true) + end + end + @test isempty(readdir(dir)) # make sure everything got cleaned up + end +end + # issue #10506 #10434 ## Tests for directories and links to directories if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER @@ -1472,7 +1769,7 @@ rm(dir) ################## -# Return values of mkpath, mkdir, cp, mv and touch +# Return values of mkpath, mkdir, cp, mv, rename and touch #################### mktempdir() do dir name1 = joinpath(dir, "apples") @@ -1489,6 +1786,9 @@ mktempdir() do dir @test cp(name2, name1) == name1 @test isfile(name1) @test isfile(name2) + @test Base.rename(name1, name2) == name2 + @test !ispath(name1) + @test isfile(name2) namedir = joinpath(dir, "chalk") namepath = joinpath(dir, "chalk", "cheese", "fresh") @test !ispath(namedir) From 3b97dda8a59bcf3f776b32a3a305cce22ee31625 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 31 Aug 2024 11:14:02 -0400 Subject: [PATCH 66/94] Even MORE tests for transcode --- test/strings/basic.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/strings/basic.jl b/test/strings/basic.jl index a7266f52f16fc..874607f3c1b20 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -1398,9 +1398,14 @@ end str_2 = "αβγ" # string starting with a 3 byte UTF-8 character str_3 = "आख" - @testset for str in (str_1, str_2, str_3) + # string starting with a 4 byte UTF-8 character + str_4 = "𒃵𒃰" + @testset for str in (str_1, str_2, str_3, str_4) + @test transcode(String, str) === str @test transcode(String, transcode(UInt16, str)) == str @test transcode(String, transcode(UInt16, transcode(UInt8, str))) == str + @test transcode(String, transcode(Int32, transcode(UInt8, str))) == str + @test transcode(String, transcode(UInt32, transcode(UInt8, str))) == str @test transcode(String, transcode(UInt8, transcode(UInt16, str))) == str end end From d81a45bf4e74abec2f4ff3a9fe435d8f64100b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= <765740+giordano@users.noreply.github.com> Date: Sat, 31 Aug 2024 22:22:26 +0200 Subject: [PATCH 67/94] [LibUV_jll] Update to new build (#55647) Corresponding PR to Yggdrasil: https://github.com/JuliaPackaging/Yggdrasil/pull/9337. This build includes backports of https://github.com/libuv/libuv/pull/4278 (useful for for #46226) and https://github.com/libuv/libuv/pull/4521 (useful for #55592) --- deps/checksums/libuv | 68 +++++++++++++++++------------------ deps/libuv.version | 4 ++- stdlib/LibUV_jll/Project.toml | 2 +- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/deps/checksums/libuv b/deps/checksums/libuv index 41a9a5bdf9722..c2c86a9767463 100644 --- a/deps/checksums/libuv +++ b/deps/checksums/libuv @@ -1,34 +1,34 @@ -LibUV.v2.0.1+16.aarch64-apple-darwin.tar.gz/md5/132266a501144f34eb9b8d5199db43c0 -LibUV.v2.0.1+16.aarch64-apple-darwin.tar.gz/sha512/e466ba8a2fe916f0e2dccb1d1075a6a20fcc5d5068d2375c940353a63522332fa8f665461adbb47ad4d30dabaea011b8e72a603601da29a071d98c7d7d130f46 -LibUV.v2.0.1+16.aarch64-linux-gnu.tar.gz/md5/1ae3018d9ab8bb293dbf6277c2c209cc -LibUV.v2.0.1+16.aarch64-linux-gnu.tar.gz/sha512/6e56876cdf0fdad1aade6435edf980b286438ee9fa695fa4e262b47f7ada6ff69535c59d216daee3eb1d061a90c2c16fd70d21438776c54addda93cf275ef1be -LibUV.v2.0.1+16.aarch64-linux-musl.tar.gz/md5/08243e727c7e957f5972a200b5d89113 -LibUV.v2.0.1+16.aarch64-linux-musl.tar.gz/sha512/4a684f248704b16b882d66ed7af60e2217a0b98f476bfdd1cb545d3e2adb17f6a410bf09e270c1e2623e550b36639c9282a562ab415850dfea98736ec03fd000 -LibUV.v2.0.1+16.armv6l-linux-gnueabihf.tar.gz/md5/c4dfccf5a899782715cbb0ca0197938c -LibUV.v2.0.1+16.armv6l-linux-gnueabihf.tar.gz/sha512/ecdcd655865a532187e4e98cb21ca68e62303813cad585de83382aa226d965213f24fe7a684e1189fad11b0e5f2f4b318c122f557a6117f61bb2948b51e16a76 -LibUV.v2.0.1+16.armv6l-linux-musleabihf.tar.gz/md5/5382dae963f3003aefdb119377a45e82 -LibUV.v2.0.1+16.armv6l-linux-musleabihf.tar.gz/sha512/f901c2965e8f9ca52900180c32cdb70d8adc13f12f076c1b109d57b749cac1ecaac3c72e22531e6fcb79c8f2c7cf952ff563779d3764b015b73db079f2b171cb -LibUV.v2.0.1+16.armv7l-linux-gnueabihf.tar.gz/md5/9c4cd82249c03ebeac670e2c7c8c1078 -LibUV.v2.0.1+16.armv7l-linux-gnueabihf.tar.gz/sha512/ee4b7f866e3f63df303d00d48d36680c490570979bb7174c12cfcf9efaf48ea7ae90aa05b41da8ab686de93c910c5a761f31da22845ad48fd980e9c16437cbfb -LibUV.v2.0.1+16.armv7l-linux-musleabihf.tar.gz/md5/5255d7e320ef37eb63d0e85c4b86d20d -LibUV.v2.0.1+16.armv7l-linux-musleabihf.tar.gz/sha512/5bcd3d22b1e2398879e654bb550fd093891775c64cb48bd179c4f9ff8dcbff23eda91a66ea14852ef5945d5c114732957075e3b3fded4cbd3cca559fead842db -LibUV.v2.0.1+16.i686-linux-gnu.tar.gz/md5/7f0fc52beb13dad773c6ab54deee7a62 -LibUV.v2.0.1+16.i686-linux-gnu.tar.gz/sha512/cb1736eab4fa1be89018b3c77c3551a99d0fa761ad2f1947587c215d87d963d43198ce87574b6eb9d1fb8a93abf1ae89e74fb8a3f3fb9c4fd08a49e04b4335f4 -LibUV.v2.0.1+16.i686-linux-musl.tar.gz/md5/ed22ccd7eaa09ed9c71afc0c6affa423 -LibUV.v2.0.1+16.i686-linux-musl.tar.gz/sha512/7f3ff061c3d7d0c3c0c0be3e4052aeed39f35e1ba0b92f3ee3d9f266f26d064acc153c08054a22d090167f00fef3c27ec54e836de35f348e4849baab301f7fa4 -LibUV.v2.0.1+16.i686-w64-mingw32.tar.gz/md5/7f1fe93df0b741ca30c4fb64ff9ac9bd -LibUV.v2.0.1+16.i686-w64-mingw32.tar.gz/sha512/9d71722c538d8232d8510fa2a43e7a52271b078401dfa838de9eedcfc34a2483aa3b1c221b17c41353b54554fe76d86b4973c5261b288228a91f0cc92820ad93 -LibUV.v2.0.1+16.powerpc64le-linux-gnu.tar.gz/md5/b796de6c75f18f318823e3e1cdd316c8 -LibUV.v2.0.1+16.powerpc64le-linux-gnu.tar.gz/sha512/f8dbb98cb49edfa06a0b48fbe1e658ca5a9bca13fe33d21872a012deaa1052a495faf74f90c0dfa48378b9f4f51f1045e01e563aec427d8c89d50e4eef0e4938 -LibUV.v2.0.1+16.x86_64-apple-darwin.tar.gz/md5/f2d55b315fa1f77b632a461530bb6b3b -LibUV.v2.0.1+16.x86_64-apple-darwin.tar.gz/sha512/eb40a193c3bca5e822a417879e854877b353a2a04b03a721ef4125360f1189a3685d2751e2f975360a2ad4c37e6043485a54b5349b3da423b8aae73d4a095d04 -LibUV.v2.0.1+16.x86_64-linux-gnu.tar.gz/md5/a573ded4f78f8677ef73594be9629638 -LibUV.v2.0.1+16.x86_64-linux-gnu.tar.gz/sha512/c5809635be3ab5dc53c37a028e58695d89ea91eee850af22a0e8db10ea021640f1e618a553848332ee6df66eecd08d34605e335aad46ece82365a3525b69c42f -LibUV.v2.0.1+16.x86_64-linux-musl.tar.gz/md5/5bdad561b5db7d19f198ef090ae3ec84 -LibUV.v2.0.1+16.x86_64-linux-musl.tar.gz/sha512/6662c8226f22f79f8c40857a5a531841f013031dd2e9536568498bfd536f133976ff71d0cc5f56f1e0c0b7f2403a35c2ccef9117d9e0d7819771bd492194f20d -LibUV.v2.0.1+16.x86_64-unknown-freebsd.tar.gz/md5/f4ad9e445e4b14e2b59b2b77c9ed72ad -LibUV.v2.0.1+16.x86_64-unknown-freebsd.tar.gz/sha512/a78deac6d8321f274a229961620da4d069ff2accf7d1ed9edfb01c21ad47eb33d364ba2f310ff4a93b2732dcd16f6d481843dbcb273770d731fd528f9c7a9ddc -LibUV.v2.0.1+16.x86_64-w64-mingw32.tar.gz/md5/72caa067cf24e304955405dcb4de195a -LibUV.v2.0.1+16.x86_64-w64-mingw32.tar.gz/sha512/de80ca98d199d3c5626ebc771325806ce3aae5927220201c2351207c10ff67791d2865f76e41519df88f0be3da534342965e7ba0d055d807c4b2b6c78bd2427d -libuv-ca3a5a431a1c37859b6508e6b2a288092337029a.tar.gz/md5/d1fbca8bcc5819037b8b81ae4f61c357 -libuv-ca3a5a431a1c37859b6508e6b2a288092337029a.tar.gz/sha512/e735861923c0fc597b53eb2efb56b26acec29e3fcae7e76d349fc08f8b9d340df9ac60a1cd245e46a434aa357ed8e377734c1c97bf08bd044c9ba0c02b082a6a +LibUV.v2.0.1+17.aarch64-apple-darwin.tar.gz/md5/f176c76e5e2096dea8443302cf9550b8 +LibUV.v2.0.1+17.aarch64-apple-darwin.tar.gz/sha512/4301b13953a08a758b86e30af3261fd9a291ce3829b4d98e71e2a2c040e322e284c5a6eb4bc7189cc352f4b1cf7041e2cfd3380d511d88c151df3101ad74594e +LibUV.v2.0.1+17.aarch64-linux-gnu.tar.gz/md5/c81515783363702a1bd4b65fd6d7f36b +LibUV.v2.0.1+17.aarch64-linux-gnu.tar.gz/sha512/011429365337f5a45e56ca7a42709866bb994c747a1170d870f5f3ddfff2d36138866ee9278ac01172bc71bde8dee404bcb9cae9c7b44222bf1cc883659df269 +LibUV.v2.0.1+17.aarch64-linux-musl.tar.gz/md5/e74d5ea4912dd326b2705638faa7b805 +LibUV.v2.0.1+17.aarch64-linux-musl.tar.gz/sha512/a26a9f2c9051816230324071c502321f7af3885d581a400615858a93a4cd457226048d15b0e7f6a73d12659763c705b5ab519e9f5b35c6d886b9fd5babbfe352 +LibUV.v2.0.1+17.armv6l-linux-gnueabihf.tar.gz/md5/6df38bcf5d0a61dee63d16b73d0c9a24 +LibUV.v2.0.1+17.armv6l-linux-gnueabihf.tar.gz/sha512/d5354a6532061de0a58965ce0e427bde52f9ae0ee39a98e1a33de4c414fddcba9ba139ddf91be7321a4ccc97bbf7a8a8357ff10cf60f83c0a6bff7d839d6d7a8 +LibUV.v2.0.1+17.armv6l-linux-musleabihf.tar.gz/md5/6f02a24cfbfae3032fadceaea1faed39 +LibUV.v2.0.1+17.armv6l-linux-musleabihf.tar.gz/sha512/7fd107eb9a5ea84b488ea02e4fbedc9fe13bb11be859986a47af38f40ad775dd9f738c790878a3503437bcac1eb26ad9fe26f4aa0d3cb45c980b4c5abc9aec99 +LibUV.v2.0.1+17.armv7l-linux-gnueabihf.tar.gz/md5/96b09dec72f7e9b7409fa2920e67c866 +LibUV.v2.0.1+17.armv7l-linux-gnueabihf.tar.gz/sha512/6a0f79fc15c944fabba5c65180b665bc9769c6ff25863e330049f48b3a4394b448492f5a9a76bb7f8dbd3ce44dfc6f9ccdc2c71c42e1c749e88070fe99b1db69 +LibUV.v2.0.1+17.armv7l-linux-musleabihf.tar.gz/md5/f44e4b2521a813181f943895bdb0dd3c +LibUV.v2.0.1+17.armv7l-linux-musleabihf.tar.gz/sha512/cda1413dca817f772e8b343db0c6de0ef6b8f269e9a6a2ef3403c2582aeab554f46281bbb1eb4659c259198ef47fe26aab648a281e66f80aaf2f2cda0a23ac05 +LibUV.v2.0.1+17.i686-linux-gnu.tar.gz/md5/1f231d89cf9c04515d2d107a5d786cc8 +LibUV.v2.0.1+17.i686-linux-gnu.tar.gz/sha512/089cb8a372cdee0cbc0e78fc201611bb9bafd99af9a78e09d6097a6b70e7c4aa001ebd86f944b0a885c072093c529bf86bcaa32bde4fc1934407a858c1a5a764 +LibUV.v2.0.1+17.i686-linux-musl.tar.gz/md5/01cfc2a9e2536dbd330267917abb19ce +LibUV.v2.0.1+17.i686-linux-musl.tar.gz/sha512/72f3588cb464a60e61f8998242aaa2abdf93df920a2feba5e1d66ef0f2498488df0ec415cbb499d7f75c47bdfc7e3a2fdda6a94383492e0ad13e464eb1314ff8 +LibUV.v2.0.1+17.i686-w64-mingw32.tar.gz/md5/8ba829adad6a45dd71d5f25a7f958e59 +LibUV.v2.0.1+17.i686-w64-mingw32.tar.gz/sha512/dff3b1cfe54e583f8e9536ed02e56180b2cdb391bd108559ed97b2cafa743ebade9ddf04580912436e2efab747e09c79b99e33187ed67a27c5d38113373e1cec +LibUV.v2.0.1+17.powerpc64le-linux-gnu.tar.gz/md5/af0e43d9d0aa91dd82b63220d96991ef +LibUV.v2.0.1+17.powerpc64le-linux-gnu.tar.gz/sha512/9fabe3089e4fc60e910770c32d36300ce8ef36c28e8cc9c72fbecba6eb80285ee8174e84e4452fb4ce90ee7c7f94e99b03fce47d8c579bd614bfffd553f93666 +LibUV.v2.0.1+17.x86_64-apple-darwin.tar.gz/md5/871040e874eedae54553d8f1c91b9133 +LibUV.v2.0.1+17.x86_64-apple-darwin.tar.gz/sha512/d5eee08b65e4bb8b444c61ac277bec9ef944b9279dd7f0732b3cd91d47788c77938e5db71e019e01bbe7785a75df95faf14368764f700c6b7a6b9e4d96d6b4c2 +LibUV.v2.0.1+17.x86_64-linux-gnu.tar.gz/md5/d2d186952c6d017fe33f6a6bea63a3ea +LibUV.v2.0.1+17.x86_64-linux-gnu.tar.gz/sha512/15501534bf5721e6bb668aabe6dc6375349f7a284e28df0609d00982e7e456908bd6868722391afa7f44a5c82faedc8cf544f69a0e4fb9fb0d529b3ae3d44d78 +LibUV.v2.0.1+17.x86_64-linux-musl.tar.gz/md5/271d4d40a1ae53ed5b2376e5936cfcf9 +LibUV.v2.0.1+17.x86_64-linux-musl.tar.gz/sha512/1956f059ed01f66b72349d6561b04e6a89b7257c0f838d7fbdd2cee79bd126bb46b93bf944a042b5a6a235762a7a0cdd117207342dd55a0c58653a70b4a38d48 +LibUV.v2.0.1+17.x86_64-unknown-freebsd.tar.gz/md5/62fe8523948914fbe7e28bf0b8d73594 +LibUV.v2.0.1+17.x86_64-unknown-freebsd.tar.gz/sha512/e6486888028c96975f74bc9313cba9706f6bf2be085aa776c44cbb2886753b2eee62469a0be92eb0542df1d0f51db3b34c7ba5e46842e16c6ff1d20e11b75322 +LibUV.v2.0.1+17.x86_64-w64-mingw32.tar.gz/md5/541601cf27a1d28d25b9ffb00f5aed16 +LibUV.v2.0.1+17.x86_64-w64-mingw32.tar.gz/sha512/58fd5b54694dbddc2abaccfa7291955c75818cdedbd5d9057025bc3e3e6511812e7b03f71cd839c70cd39e77c5ba3e19a07dccd579e2148a2d212fa3829a9a76 +libuv-c57e7f06cbe697ca8ea9215ce054a608c451b193.tar.gz/md5/2b81dbf80d7a9fd10806d1705dbccb7f +libuv-c57e7f06cbe697ca8ea9215ce054a608c451b193.tar.gz/sha512/6796960a58031fa2bc9d72c8f35dff9f213e8d36bbc21ddb65f59bb4dfb074fc18414aece5a046a882dd08b42d5bd32277560c07a1298f68ab8bcd8aadcbf50b diff --git a/deps/libuv.version b/deps/libuv.version index bc8e2e57c9517..db6afd9f1bb51 100644 --- a/deps/libuv.version +++ b/deps/libuv.version @@ -1,7 +1,9 @@ +# -*- makefile -*- + ## jll artifact LIBUV_JLL_NAME := LibUV ## source build LIBUV_VER := 2 LIBUV_BRANCH=julia-uv2-1.48.0 -LIBUV_SHA1=ca3a5a431a1c37859b6508e6b2a288092337029a +LIBUV_SHA1=c57e7f06cbe697ca8ea9215ce054a608c451b193 diff --git a/stdlib/LibUV_jll/Project.toml b/stdlib/LibUV_jll/Project.toml index 7c61fdf89df70..0c1ad8f25f848 100644 --- a/stdlib/LibUV_jll/Project.toml +++ b/stdlib/LibUV_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibUV_jll" uuid = "183b4373-6708-53ba-ad28-60e28bb38547" -version = "2.0.1+16" +version = "2.0.1+17" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" From 13283252747dce9081003f270de48df88d548290 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sun, 1 Sep 2024 05:31:04 +0900 Subject: [PATCH 68/94] fix new type instability from `getindex(::String, r::UnitRange{Int})` (#55625) --- base/compiler/typeinfer.jl | 5 +++-- base/strings/string.jl | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index e2f2a1f2cc975..315a068e611fe 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -864,7 +864,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize end end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_output(#=incremental=#false) - add_remark!(interp, caller, "Inference is disabled for the target module") + add_remark!(interp, caller, "[typeinf_edge] Inference is disabled for the target module") return EdgeCallResult(Any, Any, nothing, Effects()) end if !is_cached(caller) && frame_parent(caller) === nothing @@ -897,7 +897,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize end frame = InferenceState(result, cache_mode, interp) # always use the cache for edge targets if frame === nothing - add_remark!(interp, caller, "Failed to retrieve source") + add_remark!(interp, caller, "[typeinf_edge] Failed to retrieve source") # can't get the source for this, so we know nothing if cache_mode == CACHE_MODE_GLOBAL engine_reject(interp, ci) @@ -918,6 +918,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize return EdgeCallResult(frame.bestguess, exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle + add_remark!(interp, caller, "[typeinf_edge] Unresolvable cycle") return EdgeCallResult(Any, Any, nothing, Effects()) end # return the current knowledge about this cycle diff --git a/base/strings/string.jl b/base/strings/string.jl index f5abbead34bd1..90d6e5b26ccd3 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -208,7 +208,7 @@ end i = i′ @inbounds l = codeunit(s, i) (l < 0x80) | (0xf8 ≤ l) && return i+1 - @assert l >= 0xc0 + @assert l >= 0xc0 "invalid codeunit" end # first continuation byte (i += 1) > n && return i From 4b99e990259cbb35e1e9d8ce1ba25eea1c5d6ec5 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Sun, 1 Sep 2024 10:32:00 -0400 Subject: [PATCH 69/94] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20Pk?= =?UTF-8?q?g=20stdlib=20from=2043e7849ce=20to=20299a35610=20(#55659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pkg-299a356100f54215388502148979189aff760822.tar.gz/md5 | 1 + .../Pkg-299a356100f54215388502148979189aff760822.tar.gz/sha512 | 1 + .../Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 | 1 - .../Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 | 1 - stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/md5 create mode 100644 deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/sha512 delete mode 100644 deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 diff --git a/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/md5 b/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/md5 new file mode 100644 index 0000000000000..3c112b99f88d9 --- /dev/null +++ b/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/md5 @@ -0,0 +1 @@ +791c9ca37077fdc36b959a17904dd935 diff --git a/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/sha512 b/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/sha512 new file mode 100644 index 0000000000000..c7c212047d2b0 --- /dev/null +++ b/deps/checksums/Pkg-299a356100f54215388502148979189aff760822.tar.gz/sha512 @@ -0,0 +1 @@ +96520326931685d4300e825a302010f113e942aaa55aa4ff12caf3e9df314309df993c97753ae482c2198db67678423885bf5ea40c743c8e4b6ef96d7b8d4472 diff --git a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 deleted file mode 100644 index 2d5f5888e777f..0000000000000 --- a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -d992a5c629199747d68baa1593a7c37d diff --git a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 b/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 deleted file mode 100644 index 4201ee05347a7..0000000000000 --- a/deps/checksums/Pkg-43e7849ce37545493d0da3226cd7449f5f88563e.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -27ea738dbc4db8e4a02b00fbbdc4e2047906fe0561dd4c7f9e5ce5ea9b0b7b8ef9e29234f8e435deaa6cb3e29861130b06cb0da11118c40d78f4c475ac48db3f diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index 60d2914b7f853..3d4a627d6e472 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = 43e7849ce37545493d0da3226cd7449f5f88563e +PKG_SHA1 = 299a356100f54215388502148979189aff760822 PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From 58d5263f3d6f8177a14559f4779fcf72c1065b04 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 2 Sep 2024 08:47:59 +0530 Subject: [PATCH 70/94] Fix errant lmul! for tridiagonal and triangular (#55546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method is incorrect. Firstly, the current implementation doesn't act in-place. Secondly, the result can't be stored in-place into a triangular matrix in general. This PR changes the implementation to convert the tridiagonal matrix to a `Diagonal` or a `Bidiagonal` one before attempting the `lmul!`. Currently, ```julia julia> T = Tridiagonal([1,2], [1,2,3], [1,2]); U = UpperTriangular(fill(2, 3, 3)); julia> lmul!(T, U) 3×3 Matrix{Int64}: 2 4 4 2 6 10 0 4 10 julia> U # not updated 3×3 UpperTriangular{Int64, Matrix{Int64}}: 2 2 2 ⋅ 2 2 ⋅ ⋅ 2 julia> parent(U) # except for the underlying storage 3×3 Matrix{Int64}: 2 2 2 0 2 2 0 0 2 ``` After this, ```julia julia> lmul!(T, U) ERROR: ArgumentError: matrix cannot be represented as Bidiagonal ``` I'm unsure if we want this method at all, since there isn't a corresponding `rmul!`, but I've left it there to avoid breakages, if any. --- stdlib/LinearAlgebra/src/triangular.jl | 2 -- stdlib/LinearAlgebra/test/triangular.jl | 2 -- 2 files changed, 4 deletions(-) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 923e13e488c85..8d32dac824ce8 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -953,8 +953,6 @@ isunit_char(::UnitUpperTriangular) = 'U' isunit_char(::LowerTriangular) = 'N' isunit_char(::UnitLowerTriangular) = 'U' -lmul!(A::Tridiagonal, B::AbstractTriangular) = A*full!(B) - # generic fallback for AbstractTriangular matrices outside of the four subtypes provided here _trimul!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVector) = lmul!(A, copyto!(C, B)) diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index 5f0a829f9cdda..b88be00b0209c 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -442,8 +442,6 @@ Base.getindex(A::MyTriangular, i::Int, j::Int) = A.data[i,j] debug && println("elty1: $elty1, A1: $t1, B: $eltyB") - Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) - @test lmul!(Tri,copy(A1)) ≈ Tri*M1 Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) C = Matrix{promote_type(elty1,eltyB)}(undef, n, n) mul!(C, Tri, A1) From 6170c4b5fe820d9651d61f7e6d3a03ac2800d755 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 2 Sep 2024 17:09:31 +0530 Subject: [PATCH 71/94] Improve type-stability in SymTridiagonal triu!/tril! (#55646) Changing the final `elseif` branch to an `else` makes it clear that the method definite returns a value, and the returned type is now a `Tridiagonal` instead of a `Union{Nothing, Tridiagonal}` --- stdlib/LinearAlgebra/src/tridiag.jl | 4 +-- stdlib/LinearAlgebra/test/tridiag.jl | 48 ++++++++++++++++++---------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 3f8eb5da9fc9d..84c79f57debc7 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -372,7 +372,7 @@ function tril!(M::SymTridiagonal{T}, k::Integer=0) where T return Tridiagonal(M.ev,M.dv,zero(M.ev)) elseif k == 0 return Tridiagonal(M.ev,M.dv,zero(M.ev)) - elseif k >= 1 + else # if k >= 1 return Tridiagonal(M.ev,M.dv,copy(M.ev)) end end @@ -391,7 +391,7 @@ function triu!(M::SymTridiagonal{T}, k::Integer=0) where T return Tridiagonal(zero(M.ev),M.dv,M.ev) elseif k == 0 return Tridiagonal(zero(M.ev),M.dv,M.ev) - elseif k <= -1 + else # if k <= -1 return Tridiagonal(M.ev,M.dv,copy(M.ev)) end end diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index 759d692f8bc68..3330fa682fe5e 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -135,27 +135,43 @@ end @test_throws ArgumentError tril!(SymTridiagonal(d, dl), n) @test_throws ArgumentError tril!(Tridiagonal(dl, d, du), -n - 2) @test_throws ArgumentError tril!(Tridiagonal(dl, d, du), n) - @test tril(SymTridiagonal(d,dl)) == Tridiagonal(dl,d,zerosdl) - @test tril(SymTridiagonal(d,dl),1) == Tridiagonal(dl,d,dl) - @test tril(SymTridiagonal(d,dl),-1) == Tridiagonal(dl,zerosd,zerosdl) - @test tril(SymTridiagonal(d,dl),-2) == Tridiagonal(zerosdl,zerosd,zerosdl) - @test tril(Tridiagonal(dl,d,du)) == Tridiagonal(dl,d,zerosdu) - @test tril(Tridiagonal(dl,d,du),1) == Tridiagonal(dl,d,du) - @test tril(Tridiagonal(dl,d,du),-1) == Tridiagonal(dl,zerosd,zerosdu) - @test tril(Tridiagonal(dl,d,du),-2) == Tridiagonal(zerosdl,zerosd,zerosdu) + @test @inferred(tril(SymTridiagonal(d,dl))) == Tridiagonal(dl,d,zerosdl) + @test @inferred(tril(SymTridiagonal(d,dl),1)) == Tridiagonal(dl,d,dl) + @test @inferred(tril(SymTridiagonal(d,dl),-1)) == Tridiagonal(dl,zerosd,zerosdl) + @test @inferred(tril(SymTridiagonal(d,dl),-2)) == Tridiagonal(zerosdl,zerosd,zerosdl) + @test @inferred(tril(Tridiagonal(dl,d,du))) == Tridiagonal(dl,d,zerosdu) + @test @inferred(tril(Tridiagonal(dl,d,du),1)) == Tridiagonal(dl,d,du) + @test @inferred(tril(Tridiagonal(dl,d,du),-1)) == Tridiagonal(dl,zerosd,zerosdu) + @test @inferred(tril(Tridiagonal(dl,d,du),-2)) == Tridiagonal(zerosdl,zerosd,zerosdu) + @test @inferred(tril!(copy(SymTridiagonal(d,dl)))) == Tridiagonal(dl,d,zerosdl) + @test @inferred(tril!(copy(SymTridiagonal(d,dl)),1)) == Tridiagonal(dl,d,dl) + @test @inferred(tril!(copy(SymTridiagonal(d,dl)),-1)) == Tridiagonal(dl,zerosd,zerosdl) + @test @inferred(tril!(copy(SymTridiagonal(d,dl)),-2)) == Tridiagonal(zerosdl,zerosd,zerosdl) + @test @inferred(tril!(copy(Tridiagonal(dl,d,du)))) == Tridiagonal(dl,d,zerosdu) + @test @inferred(tril!(copy(Tridiagonal(dl,d,du)),1)) == Tridiagonal(dl,d,du) + @test @inferred(tril!(copy(Tridiagonal(dl,d,du)),-1)) == Tridiagonal(dl,zerosd,zerosdu) + @test @inferred(tril!(copy(Tridiagonal(dl,d,du)),-2)) == Tridiagonal(zerosdl,zerosd,zerosdu) @test_throws ArgumentError triu!(SymTridiagonal(d, dl), -n) @test_throws ArgumentError triu!(SymTridiagonal(d, dl), n + 2) @test_throws ArgumentError triu!(Tridiagonal(dl, d, du), -n) @test_throws ArgumentError triu!(Tridiagonal(dl, d, du), n + 2) - @test triu(SymTridiagonal(d,dl)) == Tridiagonal(zerosdl,d,dl) - @test triu(SymTridiagonal(d,dl),-1) == Tridiagonal(dl,d,dl) - @test triu(SymTridiagonal(d,dl),1) == Tridiagonal(zerosdl,zerosd,dl) - @test triu(SymTridiagonal(d,dl),2) == Tridiagonal(zerosdl,zerosd,zerosdl) - @test triu(Tridiagonal(dl,d,du)) == Tridiagonal(zerosdl,d,du) - @test triu(Tridiagonal(dl,d,du),-1) == Tridiagonal(dl,d,du) - @test triu(Tridiagonal(dl,d,du),1) == Tridiagonal(zerosdl,zerosd,du) - @test triu(Tridiagonal(dl,d,du),2) == Tridiagonal(zerosdl,zerosd,zerosdu) + @test @inferred(triu(SymTridiagonal(d,dl))) == Tridiagonal(zerosdl,d,dl) + @test @inferred(triu(SymTridiagonal(d,dl),-1)) == Tridiagonal(dl,d,dl) + @test @inferred(triu(SymTridiagonal(d,dl),1)) == Tridiagonal(zerosdl,zerosd,dl) + @test @inferred(triu(SymTridiagonal(d,dl),2)) == Tridiagonal(zerosdl,zerosd,zerosdl) + @test @inferred(triu(Tridiagonal(dl,d,du))) == Tridiagonal(zerosdl,d,du) + @test @inferred(triu(Tridiagonal(dl,d,du),-1)) == Tridiagonal(dl,d,du) + @test @inferred(triu(Tridiagonal(dl,d,du),1)) == Tridiagonal(zerosdl,zerosd,du) + @test @inferred(triu(Tridiagonal(dl,d,du),2)) == Tridiagonal(zerosdl,zerosd,zerosdu) + @test @inferred(triu!(copy(SymTridiagonal(d,dl)))) == Tridiagonal(zerosdl,d,dl) + @test @inferred(triu!(copy(SymTridiagonal(d,dl)),-1)) == Tridiagonal(dl,d,dl) + @test @inferred(triu!(copy(SymTridiagonal(d,dl)),1)) == Tridiagonal(zerosdl,zerosd,dl) + @test @inferred(triu!(copy(SymTridiagonal(d,dl)),2)) == Tridiagonal(zerosdl,zerosd,zerosdl) + @test @inferred(triu!(copy(Tridiagonal(dl,d,du)))) == Tridiagonal(zerosdl,d,du) + @test @inferred(triu!(copy(Tridiagonal(dl,d,du)),-1)) == Tridiagonal(dl,d,du) + @test @inferred(triu!(copy(Tridiagonal(dl,d,du)),1)) == Tridiagonal(zerosdl,zerosd,du) + @test @inferred(triu!(copy(Tridiagonal(dl,d,du)),2)) == Tridiagonal(zerosdl,zerosd,zerosdu) @test !istril(SymTridiagonal(d,dl)) @test istril(SymTridiagonal(d,zerosdl)) From ae050a674dd632b2dfe6ede45b876980b0f86457 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 2 Sep 2024 17:10:10 +0530 Subject: [PATCH 72/94] Reuse size-check function from `lacpy!` in `copytrito!` (#55664) Since there is a size-check function in `lacpy!` that does the same thing, we may reuse it instead of duplicating the check --- stdlib/LinearAlgebra/src/generic.jl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index f60016125d2e9..e5f23b4981616 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -2014,20 +2014,12 @@ function copytrito!(B::AbstractMatrix, A::AbstractMatrix, uplo::AbstractChar) m1,n1 = size(B) A = Base.unalias(B, A) if uplo == 'U' - if n < m - (m1 < n || n1 < n) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least size ($n,$n)")) - else - (m1 < m || n1 < n) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least size ($m,$n)")) - end + LAPACK.lacpy_size_check((m1, n1), (n < m ? n : m, n)) for j in 1:n, i in 1:min(j,m) @inbounds B[i,j] = A[i,j] end else # uplo == 'L' - if m < n - (m1 < m || n1 < m) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least size ($m,$m)")) - else - (m1 < m || n1 < n) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least size ($m,$n)")) - end + LAPACK.lacpy_size_check((m1, n1), (m, m < n ? m : n)) for j in 1:n, i in j:m @inbounds B[i,j] = A[i,j] end From 3a2a4d8dd74d4d5780a78e059f767578bb376618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Vanuxem?= <42774576+gvanuxem@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:59:18 +0200 Subject: [PATCH 73/94] Update calling-c-and-fortran-code.md: fix ccall parameters (not a tuple) (#55665) --- doc/src/manual/calling-c-and-fortran-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/calling-c-and-fortran-code.md b/doc/src/manual/calling-c-and-fortran-code.md index 6f4d69b16bc81..b8d064f698208 100644 --- a/doc/src/manual/calling-c-and-fortran-code.md +++ b/doc/src/manual/calling-c-and-fortran-code.md @@ -996,7 +996,7 @@ A table of translations between the macro and function interfaces is given below |------------------------------------------------------------------------------|-----------------------------------------------------------------------------| | `@ccall clock()::Int32` | `ccall(:clock, Int32, ())` | | `@ccall f(a::Cint)::Cint` | `ccall(:a, Cint, (Cint,), a)` | -| `@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoid` | `ccall((:f, "mylib"), Cvoid, (Cint, Cdouble), (a, b))` | +| `@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoid` | `ccall((:f, "mylib"), Cvoid, (Cint, Cdouble), a, b)` | | `@ccall $fptr.f()::Cvoid` | `ccall(fptr, f, Cvoid, ())` | | `@ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint` | `` | | `@ccall printf("%s = %s\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cint` | `ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5")` | From 5c706af84ab0cace899e81272258f5b3ee3eb468 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Mon, 2 Sep 2024 16:32:52 +0200 Subject: [PATCH 74/94] Allow exact redefinition for types with recursive supertype reference (#55380) This PR allows redefining a type when the new type is exactly identical to the previous one (like #17618, #20592 and #21024), even if the type has a reference to itself in its supertype. That particular case used to error (issue #54757), whereas with this PR: ```julia julia> struct Rec <: AbstractVector{Rec} end julia> struct Rec <: AbstractVector{Rec} end # this used to error julia> ``` Fix #54757 by implementing the solution proposed there. Hence, this should also fix downstream Revise bug https://github.com/timholy/Revise.jl/issues/813. --------- Co-authored-by: N5N3 <2642243996@qq.com> --- src/builtins.c | 3 ++ src/jltypes.c | 112 +++++++++++++++++++++++++++++++++++++++++++ src/julia_internal.h | 1 + test/core.jl | 20 ++++++++ 4 files changed, 136 insertions(+) diff --git a/src/builtins.c b/src/builtins.c index 8019ee3c0e2c6..152836bcab6a9 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2197,6 +2197,9 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb) JL_GC_PUSH2(&a, &b); a = jl_rewrap_unionall((jl_value_t*)dta->super, dta->name->wrapper); b = jl_rewrap_unionall((jl_value_t*)dtb->super, dtb->name->wrapper); + // if tb recursively refers to itself in its supertype, assume that it refers to ta + // before checking whether the supertypes are equal + b = jl_substitute_datatype(b, dtb, dta); if (!jl_types_equal(a, b)) goto no; JL_TRY { diff --git a/src/jltypes.c b/src/jltypes.c index adf39162cc7f0..fbc8e9f7f7f16 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1607,6 +1607,118 @@ jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u) return t; } +// Create a copy of type expression t where any occurrence of data type x is replaced by y. +// If x does not occur in t, return t without any copy. +// For example, jl_substitute_datatype(Foo{Bar}, Foo{T}, Qux{S}) is Qux{Bar}, with T and S +// free type variables. +// To substitute type variables, use jl_substitute_var instead. +jl_value_t *jl_substitute_datatype(jl_value_t *t, jl_datatype_t * x, jl_datatype_t * y) +{ + if jl_is_datatype(t) { + jl_datatype_t *typ = (jl_datatype_t*)t; + // For datatypes call itself recursively on the parameters to form new parameters. + // Then, if typename(t) == typename(x), rewrap the wrapper of y around the new + // parameters. Otherwise, do the same around the wrapper of t. + // This ensures that the types and supertype are properly set. + // Start by check whether there is a parameter that needs replacing. + long i_firstnewparam = -1; + size_t nparams = jl_svec_len(typ->parameters); + jl_value_t *firstnewparam = NULL; + JL_GC_PUSH1(&firstnewparam); + for (size_t i = 0; i < nparams; i++) { + jl_value_t *param = NULL; + JL_GC_PUSH1(¶m); + param = jl_svecref(typ->parameters, i); + firstnewparam = jl_substitute_datatype(param, x, y); + if (param != firstnewparam) { + i_firstnewparam = i; + JL_GC_POP(); + break; + } + JL_GC_POP(); + } + // If one of the parameters needs to be updated, or if the type name is that to + // substitute, create a new datataype + if (i_firstnewparam != -1 || typ->name == x->name) { + jl_datatype_t *uw = typ->name == x->name ? y : typ; // substitution occurs here + jl_value_t *wrapper = uw->name->wrapper; + jl_datatype_t *w = (jl_datatype_t*)jl_unwrap_unionall(wrapper); + jl_svec_t *sv = jl_alloc_svec_uninit(jl_svec_len(uw->parameters)); + JL_GC_PUSH1(&sv); + jl_value_t **vals = jl_svec_data(sv); + // no JL_GC_PUSHARGS(vals, ...) since GC is already aware of sv + for (long i = 0; i < i_firstnewparam; i++) { // copy the identical parameters + vals[i] = jl_svecref(typ->parameters, i); // value + } + if (i_firstnewparam != -1) { // insert the first non-identical parameter + vals[i_firstnewparam] = firstnewparam; + } + for (size_t i = i_firstnewparam+1; i < nparams; i++) { // insert the remaining parameters + vals[i] = jl_substitute_datatype(jl_svecref(typ->parameters, i), x, y); + } + if (jl_is_tuple_type(wrapper)) { + // special case for tuples, since the wrapper (Tuple) does not have as + // many parameters as t (it only has a Vararg instead). + t = jl_apply_tuple_type(sv, 0); + } else { + t = jl_instantiate_type_in_env((jl_value_t*)w, (jl_unionall_t*)wrapper, vals); + } + JL_GC_POP(); + } + JL_GC_POP(); + } + else if jl_is_unionall(t) { // recursively call itself on body and var bounds + jl_unionall_t* ut = (jl_unionall_t*)t; + jl_value_t *lb = NULL; + jl_value_t *ub = NULL; + jl_value_t *body = NULL; + JL_GC_PUSH3(&lb, &ub, &body); + lb = jl_substitute_datatype(ut->var->lb, x, y); + ub = jl_substitute_datatype(ut->var->ub, x, y); + body = jl_substitute_datatype(ut->body, x, y); + if (lb != ut->var->lb || ub != ut->var->ub) { + jl_tvar_t *newtvar = jl_new_typevar(ut->var->name, lb, ub); + JL_GC_PUSH1(&newtvar); + body = jl_substitute_var(body, ut->var, (jl_value_t*)newtvar); + t = jl_new_struct(jl_unionall_type, newtvar, body); + JL_GC_POP(); + } + else if (body != ut->body) { + t = jl_new_struct(jl_unionall_type, ut->var, body); + } + JL_GC_POP(); + } + else if jl_is_uniontype(t) { // recursively call itself on a and b + jl_uniontype_t *u = (jl_uniontype_t*)t; + jl_value_t *a = NULL; + jl_value_t *b = NULL; + JL_GC_PUSH2(&a, &b); + a = jl_substitute_datatype(u->a, x, y); + b = jl_substitute_datatype(u->b, x, y); + if (a != u->a || b != u->b) { + t = jl_new_struct(jl_uniontype_type, a, b); + } + JL_GC_POP(); + } + else if jl_is_vararg(t) { // recursively call itself on T + jl_vararg_t *vt = (jl_vararg_t*)t; + if (vt->T) { // vt->T could be NULL + jl_value_t *rT = NULL; + JL_GC_PUSH1(&rT); + rT = jl_substitute_datatype(vt->T, x, y); + if (rT != vt->T) { + jl_task_t *ct = jl_current_task; + t = jl_gc_alloc(ct->ptls, sizeof(jl_vararg_t), jl_vararg_type); + jl_set_typetagof((jl_vararg_t *)t, jl_vararg_tag, 0); + ((jl_vararg_t *)t)->T = rT; + ((jl_vararg_t *)t)->N = vt->N; + } + JL_GC_POP(); + } + } + return t; +} + static jl_value_t *lookup_type_stack(jl_typestack_t *stack, jl_datatype_t *tt, size_t ntp, jl_value_t **iparams) { diff --git a/src/julia_internal.h b/src/julia_internal.h index 652aae54860b5..d9e1a078c8a03 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -769,6 +769,7 @@ jl_unionall_t *jl_rename_unionall(jl_unionall_t *u); JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u); JL_DLLEXPORT jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u); +jl_value_t* jl_substitute_datatype(jl_value_t *t, jl_datatype_t * x, jl_datatype_t * y); int jl_count_union_components(jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_nth_union_component(jl_value_t *v JL_PROPAGATES_ROOT, int i) JL_NOTSAFEPOINT; int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT; diff --git a/test/core.jl b/test/core.jl index 74df09bcdfd91..4db7f0e401fa0 100644 --- a/test/core.jl +++ b/test/core.jl @@ -5611,6 +5611,26 @@ end x::Array{T} where T<:Integer end +# issue #54757, type redefinitions with recursive reference in supertype +struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N} + x::A + y::Union{A,T54757{A,N}} + z::T54757{A} +end + +struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N} + x::A + y::Union{A,T54757{A,N}} + z::T54757{A} +end + +@test_throws ErrorException struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A}},Vararg{Y,N}} where {X,Y<:T54757}, N} + x::A + y::Union{A,T54757{A,N}} + z::T54757{A} +end + + let a = Vector{Core.TypeofBottom}(undef, 2) @test a[1] == Union{} @test a == [Union{}, Union{}] From 39f2ad1e941fd092b906d60e8d23c004e8ee5b7e Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 2 Sep 2024 20:12:39 +0530 Subject: [PATCH 75/94] Reroute Symmetric/Hermitian + Diagonal through triangular (#55605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should fix the `Diagonal`-related issue from https://github.com/JuliaLang/julia/issues/55590, although the `SymTridiagonal` one still remains. ```julia julia> using LinearAlgebra julia> a = Matrix{BigFloat}(undef, 2,2) 2×2 Matrix{BigFloat}: #undef #undef #undef #undef julia> a[1] = 1; a[3] = 1; a[4] = 1 1 julia> a = Hermitian(a) 2×2 Hermitian{BigFloat, Matrix{BigFloat}}: 1.0 1.0 1.0 1.0 julia> b = Symmetric(a) 2×2 Symmetric{BigFloat, Matrix{BigFloat}}: 1.0 1.0 1.0 1.0 julia> c = Diagonal([1,1]) 2×2 Diagonal{Int64, Vector{Int64}}: 1 ⋅ ⋅ 1 julia> a+c 2×2 Hermitian{BigFloat, Matrix{BigFloat}}: 2.0 1.0 1.0 2.0 julia> b+c 2×2 Symmetric{BigFloat, Matrix{BigFloat}}: 2.0 1.0 1.0 2.0 ``` --- stdlib/LinearAlgebra/src/diagonal.jl | 15 --------------- stdlib/LinearAlgebra/src/special.jl | 19 +++++++++++++++++++ stdlib/LinearAlgebra/test/special.jl | 13 +++++++++++++ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 526ec20ddafa1..23d2422d13654 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -272,21 +272,6 @@ end (+)(Da::Diagonal, Db::Diagonal) = Diagonal(Da.diag + Db.diag) (-)(Da::Diagonal, Db::Diagonal) = Diagonal(Da.diag - Db.diag) -for f in (:+, :-) - @eval function $f(D::Diagonal{<:Number}, S::Symmetric) - return Symmetric($f(D, S.data), sym_uplo(S.uplo)) - end - @eval function $f(S::Symmetric, D::Diagonal{<:Number}) - return Symmetric($f(S.data, D), sym_uplo(S.uplo)) - end - @eval function $f(D::Diagonal{<:Real}, H::Hermitian) - return Hermitian($f(D, H.data), sym_uplo(H.uplo)) - end - @eval function $f(H::Hermitian, D::Diagonal{<:Real}) - return Hermitian($f(H.data, D), sym_uplo(H.uplo)) - end -end - (*)(x::Number, D::Diagonal) = Diagonal(x * D.diag) (*)(D::Diagonal, x::Number) = Diagonal(D.diag * x) (/)(D::Diagonal, x::Number) = Diagonal(D.diag / x) diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 5fea1e32460ff..8263e632a86b8 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -288,6 +288,25 @@ function (-)(A::UniformScaling, B::Diagonal) Diagonal(Ref(A) .- B.diag) end +for f in (:+, :-) + @eval function $f(D::Diagonal{<:Number}, S::Symmetric) + uplo = sym_uplo(S.uplo) + return Symmetric(parentof_applytri($f, Symmetric(D, uplo), S), uplo) + end + @eval function $f(S::Symmetric, D::Diagonal{<:Number}) + uplo = sym_uplo(S.uplo) + return Symmetric(parentof_applytri($f, S, Symmetric(D, uplo)), uplo) + end + @eval function $f(D::Diagonal{<:Real}, H::Hermitian) + uplo = sym_uplo(H.uplo) + return Hermitian(parentof_applytri($f, Hermitian(D, uplo), H), uplo) + end + @eval function $f(H::Hermitian, D::Diagonal{<:Real}) + uplo = sym_uplo(H.uplo) + return Hermitian(parentof_applytri($f, H, Hermitian(D, uplo)), uplo) + end +end + ## Diagonal construction from UniformScaling Diagonal{T}(s::UniformScaling, m::Integer) where {T} = Diagonal{T}(fill(T(s.λ), m)) Diagonal(s::UniformScaling, m::Integer) = Diagonal{eltype(s)}(s, m) diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 8d3733e6b1289..4b91bcfc1a4d5 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -790,6 +790,19 @@ end end end +@testset "Partly filled Hermitian and Diagonal algebra" begin + D = Diagonal([1,2]) + for S in (Symmetric, Hermitian), uplo in (:U, :L) + M = Matrix{BigInt}(undef, 2, 2) + M[1,1] = M[2,2] = M[1+(uplo == :L), 1 + (uplo == :U)] = 3 + H = S(M, uplo) + HM = Matrix(H) + @test H + D == D + H == HM + D + @test H - D == HM - D + @test D - H == D - HM + end +end + @testset "block SymTridiagonal" begin m = SizedArrays.SizedArray{(2,2)}(reshape([1:4;;],2,2)) S = SymTridiagonal(fill(m,4), fill(m,3)) From 04d6d5f2e88cc60707c2287b40a38f26e75102dc Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:31:38 +0900 Subject: [PATCH 76/94] inference: check argtype compatibility in `abstract_call_opaque_closure` (#55672) --- base/compiler/abstractinterpretation.jl | 13 +++++++++---- test/compiler/inference.jl | 13 +++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f3fc4e0423173..f2d4327668137 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2334,11 +2334,16 @@ end function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, check::Bool=true) sig = argtypes_to_type(arginfo.argtypes) - result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, si, sv) - (; rt, edge, effects, volatile_inf_result) = result tt = closure.typ - sigT = (unwrap_unionall(tt)::DataType).parameters[1] - match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) + ocargsig = rewrap_unionall((unwrap_unionall(tt)::DataType).parameters[1], tt) + ocargsig′ = unwrap_unionall(ocargsig) + ocargsig′ isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) + ocsig = rewrap_unionall(Tuple{Tuple, ocargsig′.parameters...}, ocargsig) + hasintersect(sig, ocsig) || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + ocmethod = closure.source::Method + result = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv) + (; rt, edge, effects, volatile_inf_result) = result + match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig) 𝕃ₚ = ipo_lattice(interp) ⊑ₚ = ⊑(𝕃ₚ) const_result = volatile_inf_result diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 485ee579abd52..7e1fea54830c9 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6076,3 +6076,16 @@ end fcondvarargs(a, b, c, d) = isa(d, Int64) gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, Int64) @test Core.Compiler.return_type(gcondvarargs, Tuple{Vararg{Any}}) === Bool + +# JuliaLang/julia#55627: argtypes check in `abstract_call_opaque_closure` +issue55627_some_method(x) = 2x +issue55627_make_oc() = Base.Experimental.@opaque (x::Int)->issue55627_some_method(x) + +@test Base.infer_return_type() do + f = issue55627_make_oc() + return f(1), f() +end == Union{} +@test Base.infer_return_type((Vector{Int},)) do xs + f = issue55627_make_oc() + return f(1), f(xs...) +end == Tuple{Int,Int} From 34b81fbc90a96c1db7f235a465d2cfdf5937e563 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Tue, 3 Sep 2024 13:55:10 +0530 Subject: [PATCH 77/94] Forward istriu/istril for triangular to parent (#55663) --- stdlib/LinearAlgebra/src/special.jl | 4 ++++ stdlib/LinearAlgebra/src/triangular.jl | 22 +++++++++++++++++-- stdlib/LinearAlgebra/test/triangular.jl | 28 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 8263e632a86b8..5a7c98cfdf32c 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -586,3 +586,7 @@ function cholesky(S::RealHermSymComplexHerm{<:Real,<:SymTridiagonal}, ::NoPivot B = Bidiagonal{T}(diag(S, 0), diag(S, S.uplo == 'U' ? 1 : -1), sym_uplo(S.uplo)) cholesky!(Hermitian(B, sym_uplo(S.uplo)), NoPivot(); check = check) end + +# istriu/istril for triangular wrappers of structured matrices +_istril(A::LowerTriangular{<:Any, <:BandedMatrix}, k) = istril(parent(A), k) +_istriu(A::UpperTriangular{<:Any, <:BandedMatrix}, k) = istriu(parent(A), k) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 8d32dac824ce8..71473e0dc1174 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -330,14 +330,32 @@ function Base.replace_in_print_matrix(A::Union{LowerTriangular,UnitLowerTriangul return i >= j ? s : Base.replace_with_centered_mark(s) end -Base.@constprop :aggressive function istril(A::Union{LowerTriangular,UnitLowerTriangular}, k::Integer=0) +istril(A::UnitLowerTriangular, k::Integer=0) = k >= 0 +istriu(A::UnitUpperTriangular, k::Integer=0) = k <= 0 +Base.@constprop :aggressive function istril(A::LowerTriangular, k::Integer=0) k >= 0 && return true return _istril(A, k) end -Base.@constprop :aggressive function istriu(A::Union{UpperTriangular,UnitUpperTriangular}, k::Integer=0) +@inline function _istril(A::LowerTriangular, k) + P = parent(A) + m = size(A, 1) + for j in max(1, k + 2):m + all(iszero, view(P, j:min(j - k - 1, m), j)) || return false + end + return true +end +Base.@constprop :aggressive function istriu(A::UpperTriangular, k::Integer=0) k <= 0 && return true return _istriu(A, k) end +@inline function _istriu(A::UpperTriangular, k) + P = parent(A) + m = size(A, 1) + for j in 1:min(m, m + k - 1) + all(iszero, view(P, max(1, j - k + 1):j, j)) || return false + end + return true +end istril(A::Adjoint, k::Integer=0) = istriu(A.parent, -k) istril(A::Transpose, k::Integer=0) = istriu(A.parent, -k) istriu(A::Adjoint, k::Integer=0) = istril(A.parent, -k) diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index b88be00b0209c..8748d11bd1da4 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -1226,4 +1226,32 @@ end end end +@testset "istriu/istril forwards to parent" begin + @testset "$(nameof(typeof(M)))" for M in [Tridiagonal(rand(n-1), rand(n), rand(n-1)), + Tridiagonal(zeros(n-1), zeros(n), zeros(n-1)), + Diagonal(randn(n)), + Diagonal(zeros(n)), + ] + @testset for TriT in (UpperTriangular, UnitUpperTriangular, LowerTriangular, UnitLowerTriangular) + U = TriT(M) + A = Array(U) + for k in -n:n + @test istriu(U, k) == istriu(A, k) + @test istril(U, k) == istril(A, k) + end + end + end + z = zeros(n,n) + @testset for TriT in (UpperTriangular, UnitUpperTriangular, LowerTriangular, UnitLowerTriangular) + P = Matrix{BigFloat}(undef, n, n) + copytrito!(P, z, TriT <: Union{UpperTriangular, UnitUpperTriangular} ? 'U' : 'L') + U = TriT(P) + A = Array(U) + @testset for k in -n:n + @test istriu(U, k) == istriu(A, k) + @test istril(U, k) == istril(A, k) + end + end +end + end # module TestTriangular From eebc1e4e083b9b597bef328a5bd8eeda9ec52c1f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Sep 2024 12:24:01 -0400 Subject: [PATCH 78/94] win: move stack_overflow_warning to the backtrace fiber (#55640) There is not enough stack space remaining after a stack overflow on Windows to allocate the 4k page used by `write` to call the WriteFile syscall. This causes it to hard-crash. But we can simply run this on the altstack implementation, where there is plenty of space. --- src/signals-win.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/signals-win.c b/src/signals-win.c index d7288b5d365d8..dbc3f8dad0968 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -109,6 +109,8 @@ static jl_ptls_t stkerror_ptls; static int have_backtrace_fiber; static void JL_NORETURN start_backtrace_fiber(void) { + // print the warning (this mysteriously needs a lot of stack for the WriteFile syscall) + stack_overflow_warning(); // collect the backtrace stkerror_ptls->bt_size = rec_backtrace_ctx(stkerror_ptls->bt_data, JL_MAX_BT_SIZE, stkerror_ctx, @@ -244,7 +246,6 @@ LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo) case EXCEPTION_STACK_OVERFLOW: if (ct->eh != NULL) { ptls->needs_resetstkoflw = 1; - stack_overflow_warning(); jl_throw_in_ctx(ct, jl_stackovf_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; } From e474e0b470936110f689282c62a775ed4f0f4f81 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 3 Sep 2024 16:32:48 -0300 Subject: [PATCH 79/94] Check if ct is not null before doing is_addr_on_stack in the macos signal handler. (#55603) Before the check we used to segfault while segfaulting and hang --------- Co-authored-by: Jameson Nash --- src/signals-mach.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/signals-mach.c b/src/signals-mach.c index c31b6d506b4e6..2f3e87ece296f 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -297,7 +297,9 @@ static void segv_handler(int sig, siginfo_t *info, void *context) return; } jl_task_t *ct = jl_get_current_task(); - if ((sig != SIGBUS || info->si_code == BUS_ADRERR) && is_addr_on_stack(ct, info->si_addr)) { // stack overflow and not a BUS_ADRALN (alignment error) + if ((sig != SIGBUS || info->si_code == BUS_ADRERR) && + !(ct == NULL || ct->ptls == NULL || jl_atomic_load_relaxed(&ct->ptls->gc_state) == JL_GC_STATE_WAITING || ct->eh == NULL) + && is_addr_on_stack(ct, info->si_addr)) { // stack overflow and not a BUS_ADRALN (alignment error) stack_overflow_warning(); } sigdie_handler(sig, info, context); From 48b40acf1cca3372ddaa4941b326b626146e9d16 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 3 Sep 2024 20:16:24 -0400 Subject: [PATCH 80/94] Profile.print: color Base/Core & packages. Make paths clickable (#55335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated ## This PR ![Screenshot 2024-09-02 at 1 47 23 PM](https://github.com/user-attachments/assets/1264e623-70b2-462a-a595-1db2985caf64) ## master ![Screenshot 2024-09-02 at 1 49 42 PM](https://github.com/user-attachments/assets/14d62fe1-c317-4df5-86e9-7c555f9ab6f1) Todo: - [ ] ~Maybe drop the `@` prefix when coloring it, given it's obviously special when colored~ If someone copy-pasted the profile into an issue this would make it confusing. - [ ] Figure out why `Profile.print(format=:flat)` is truncating before the terminal width is used up - [x] Make filepaths terminal links (even if they're truncated) --- NEWS.md | 3 + stdlib/Manifest.toml | 19 ++--- stdlib/Profile/Project.toml | 6 ++ stdlib/Profile/src/Allocs.jl | 2 +- stdlib/Profile/src/Profile.jl | 147 +++++++++++++++++++++------------- 5 files changed, 113 insertions(+), 64 deletions(-) diff --git a/NEWS.md b/NEWS.md index b5caaf5376fb5..95a8a51c67ac8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -138,6 +138,9 @@ Standard library changes * `Profile.take_heap_snapshot` takes a new keyword argument, `redact_data::Bool`, that is `true` by default. When set, the contents of Julia objects are not emitted in the heap snapshot. This currently only applies to strings. ([#55326]) +* `Profile.print()` now colors Base/Core/Package modules similarly to how they are in stacktraces. + Also paths, even if truncated, are now clickable in terminals that support URI links + to take you to the specified `JULIA_EDITOR` for the given file & line number. ([#55335]) #### Random diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml index c9d2086432a85..f9fb307190838 100644 --- a/stdlib/Manifest.toml +++ b/stdlib/Manifest.toml @@ -68,12 +68,12 @@ version = "1.11.0" [[deps.JuliaSyntaxHighlighting]] deps = ["StyledStrings"] uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" -version = "1.11.0" +version = "1.12.0" [[deps.LLD_jll]] deps = ["Artifacts", "Libdl", "Zlib_jll", "libLLVM_jll"] uuid = "d55e3150-da41-5e91-b323-ecfd1eec6109" -version = "16.0.6+4" +version = "18.1.7+2" [[deps.LLVMLibUnwind_jll]] deps = ["Artifacts", "Libdl"] @@ -113,12 +113,12 @@ version = "1.11.0+1" [[deps.LibUV_jll]] deps = ["Artifacts", "Libdl"] uuid = "183b4373-6708-53ba-ad28-60e28bb38547" -version = "2.0.1+16" +version = "2.0.1+17" [[deps.LibUnwind_jll]] deps = ["Artifacts", "Libdl"] uuid = "745a5e78-f969-53e9-954f-d19f2f74f4e3" -version = "1.8.1+0" +version = "1.8.1+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -163,7 +163,7 @@ version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.26+2" +version = "0.3.28+2" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] @@ -190,6 +190,7 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" [[deps.Profile]] +deps = ["StyledStrings"] uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" version = "1.11.0" @@ -223,7 +224,7 @@ version = "1.11.0" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.11.0" +version = "1.12.0" [[deps.Statistics]] deps = ["LinearAlgebra"] @@ -242,7 +243,7 @@ version = "1.11.0" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.7.0+0" +version = "7.8.0+0" [[deps.TOML]] deps = ["Dates"] @@ -281,12 +282,12 @@ version = "2.2.5+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" -version = "16.0.6+4" +version = "18.1.7+2" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] diff --git a/stdlib/Profile/Project.toml b/stdlib/Profile/Project.toml index ad0107ecf9404..13cd11f70d9b4 100644 --- a/stdlib/Profile/Project.toml +++ b/stdlib/Profile/Project.toml @@ -2,6 +2,12 @@ name = "Profile" uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" version = "1.11.0" +[deps] +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" + +[compat] +StyledStrings = "1.11.0" + [extras] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index 31d703a151ad8..9d0b18cb468ca 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -321,7 +321,7 @@ end function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) fmt.combine || error(ArgumentError("combine=false")) lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) - filenamemap = Dict{Symbol,String}() + filenamemap = Profile.FileNameMap() if isempty(lilist) warning_empty() return true diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 799f23034b9ac..a80e6c71e5aef 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -38,6 +38,8 @@ public clear, Allocs import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame +import Base: AnnotatedString +using StyledStrings: @styled_str const nmeta = 4 # number of metadata fields per block (threadid, taskid, cpu_cycle_clock, thread_sleeping) @@ -63,10 +65,10 @@ end # An internal function called to show the report after an information request (SIGINFO or SIGUSR1). function _peek_report() - iob = IOBuffer() + iob = Base.AnnotatedIOBuffer() ioc = IOContext(IOContext(iob, stderr), :displaysize=>displaysize(stderr)) print(ioc, groupby = [:thread, :task]) - Base.print(stderr, String(take!(iob))) + Base.print(stderr, read(seekstart(iob), AnnotatedString)) end # This is a ref so that it can be overridden by other profile info consumers. const peek_report = Ref{Function}(_peek_report) @@ -266,7 +268,7 @@ function print(io::IO, end any_nosamples = true if format === :tree - Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") + Base.print(io, "Overhead ╎ [+additional indent] Count File:Line Function\n") Base.print(io, "=========================================================\n") end if groupby == [:task, :thread] @@ -503,9 +505,10 @@ end # Take a file-system path and try to form a concise representation of it # based on the package ecosystem -function short_path(spath::Symbol, filenamecache::Dict{Symbol, String}) +function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,String,String}}) return get!(filenamecache, spath) do path = Base.fixup_stdlib_path(string(spath)) + possible_base_path = normpath(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) if isabspath(path) if ispath(path) # try to replace the file-system prefix with a short "@Module" one, @@ -522,20 +525,21 @@ function short_path(spath::Symbol, filenamecache::Dict{Symbol, String}) pkgid = Base.project_file_name_uuid(project_file, "") isempty(pkgid.name) && return path # bad Project file # return the joined the module name prefix and path suffix - path = path[nextind(path, sizeof(root)):end] - return string("@", pkgid.name, path) + _short_path = path[nextind(path, sizeof(root)):end] + return path, string("@", pkgid.name), _short_path end end end end - return path - elseif isfile(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) + return path, "", path + elseif isfile(possible_base_path) # do the same mechanic for Base (or Core/Compiler) files as above, # but they start from a relative path - return joinpath("@Base", normpath(path)) + return possible_base_path, "@Base", normpath(path) else # for non-existent relative paths (such as "REPL[1]"), just consider simplifying them - return normpath(path) # drop leading "./" + path = normpath(path) + return "", "", path # drop leading "./" end end end @@ -678,7 +682,7 @@ function add_fake_meta(data; threadid = 1, taskid = 0xf0f0f0f0) !isempty(data) && has_meta(data) && error("input already has metadata") cpu_clock_cycle = UInt64(99) data_with_meta = similar(data, 0) - for i = 1:length(data) + for i in eachindex(data) val = data[i] if iszero(val) # META_OFFSET_THREADID, META_OFFSET_TASKID, META_OFFSET_CPUCYCLECLOCK, META_OFFSET_SLEEPSTATE @@ -756,6 +760,8 @@ function parse_flat(::Type{T}, data::Vector{UInt64}, lidict::Union{LineInfoDict, return (lilist, n, m, totalshots, nsleeping) end +const FileNameMap = Dict{Symbol,Tuple{String,String,String}} + function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfoFlatDict}, cols::Int, fmt::ProfileFormat, threads::Union{Int,AbstractVector{Int}}, tasks::Union{UInt,AbstractVector{UInt}}, is_subsection::Bool) lilist, n, m, totalshots, nsleeping = parse_flat(fmt.combine ? StackFrame : UInt64, data, lidict, fmt.C, threads, tasks) @@ -766,7 +772,7 @@ function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfo m = m[keep] end util_perc = (1 - (nsleeping / totalshots)) * 100 - filenamemap = Dict{Symbol,String}() + filenamemap = FileNameMap() if isempty(lilist) if is_subsection Base.print(io, "Total snapshots: ") @@ -788,9 +794,34 @@ function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfo return false end +# make a terminal-clickable link to the file and linenum. +# Similar to `define_default_editors` in `Base.Filesystem` but for creating URIs not commands +function editor_link(path::String, linenum::Int) + editor = get(ENV, "JULIA_EDITOR", "") + + if editor == "code" + return "vscode://file/$path:$linenum" + elseif editor == "subl" || editor == "sublime_text" + return "subl://$path:$linenum" + elseif editor == "idea" || occursin("idea", editor) + return "idea://open?file=$path&line=$linenum" + elseif editor == "pycharm" + return "pycharm://open?file=$path&line=$linenum" + elseif editor == "atom" + return "atom://core/open/file?filename=$path&line=$linenum" + elseif editor == "emacsclient" + return "emacs://open?file=$path&line=$linenum" + elseif editor == "vim" || editor == "nvim" + return "vim://open?file=$path&line=$linenum" + else + # TODO: convert the path to a generic URI (line numbers are not supported by generic URI) + return path + end +end + function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, m::Vector{Int}, - cols::Int, filenamemap::Dict{Symbol,String}, + cols::Int, filenamemap::FileNameMap, fmt::ProfileFormat) if fmt.sortedby === :count p = sortperm(n) @@ -802,18 +833,18 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, lilist = lilist[p] n = n[p] m = m[p] - filenames = String[short_path(li.file, filenamemap) for li in lilist] + pkgnames_filenames = Tuple{String,String,String}[short_path(li.file, filenamemap) for li in lilist] funcnames = String[string(li.func) for li in lilist] wcounts = max(6, ndigits(maximum(n))) wself = max(9, ndigits(maximum(m))) maxline = 1 maxfile = 6 maxfunc = 10 - for i in 1:length(lilist) + for i in eachindex(lilist) li = lilist[i] maxline = max(maxline, li.line) - maxfunc = max(maxfunc, length(funcnames[i])) - maxfile = max(maxfile, length(filenames[i])) + maxfunc = max(maxfunc, textwidth(funcnames[i])) + maxfile = max(maxfile, sum(textwidth, pkgnames_filenames[i][2:3]) + 1) end wline = max(5, ndigits(maxline)) ntext = max(20, cols - wcounts - wself - wline - 3) @@ -829,7 +860,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, rpad("File", wfile, " "), " ", lpad("Line", wline, " "), " Function") println(io, lpad("=====", wcounts, " "), " ", lpad("========", wself, " "), " ", rpad("====", wfile, " "), " ", lpad("====", wline, " "), " ========") - for i = 1:length(n) + for i in eachindex(n) n[i] < fmt.mincount && continue li = lilist[i] Base.print(io, lpad(string(n[i]), wcounts, " "), " ") @@ -841,16 +872,29 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, Base.print(io, "[any unknown stackframes]") end else - file = filenames[i] + path, pkgname, file = pkgnames_filenames[i] isempty(file) && (file = "[unknown file]") - Base.print(io, rpad(rtruncto(file, wfile), wfile, " "), " ") + pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) + Base.printstyled(io, pkgname, color=pkgcolor) + file_trunc = ltruncate(file, max(1, wfile)) + wpad = wfile - textwidth(pkgname) + if !isempty(pkgname) && !startswith(file_trunc, "/") + Base.print(io, "/") + wpad -= 1 + end + if isempty(path) + Base.print(io, rpad(file_trunc, wpad, " ")) + else + link = editor_link(path, li.line) + Base.print(io, rpad(styled"{link=$link:$file_trunc}", wpad, " ")) + end Base.print(io, lpad(li.line > 0 ? string(li.line) : "?", wline, " "), " ") fname = funcnames[i] if !li.from_c && li.linfo !== nothing fname = sprint(show_spec_linfo, li) end isempty(fname) && (fname = "[unknown function]") - Base.print(io, ltruncto(fname, wfunc)) + Base.print(io, rtruncate(fname, wfunc)) end println(io) end @@ -889,21 +933,24 @@ function indent(depth::Int) return indent end -function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::Dict{Symbol,String}, showpointer::Bool) +# mimics Stacktraces +const PACKAGE_FIXEDCOLORS = Dict{String, Any}("@Base" => :gray, "@Core" => :gray) + +function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::FileNameMap, showpointer::Bool) nindent = min(cols>>1, level) ndigoverhead = ndigits(maxes.overhead) ndigcounts = ndigits(maxes.count) ndigline = ndigits(maximum(frame.frame.line for frame in frames)) + 6 ntext = max(30, cols - ndigoverhead - nindent - ndigcounts - ndigline - 6) widthfile = 2*ntext÷5 # min 12 - strs = Vector{String}(undef, length(frames)) + strs = Vector{AnnotatedString{String}}(undef, length(frames)) showextra = false if level > nindent nextra = level - nindent nindent -= ndigits(nextra) + 2 showextra = true end - for i = 1:length(frames) + for i in eachindex(frames) frame = frames[i] li = frame.frame stroverhead = lpad(frame.overhead > 0 ? string(frame.overhead) : "", ndigoverhead, " ") @@ -924,7 +971,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma else fname = string(li.func) end - filename = short_path(li.file, filenamemap) + path, pkgname, filename = short_path(li.file, filenamemap) if showpointer fname = string( "0x", @@ -932,17 +979,26 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma " ", fname) end - strs[i] = string(stroverhead, "╎", base, strcount, " ", - rtruncto(filename, widthfile), - ":", - li.line == -1 ? "?" : string(li.line), - "; ", - fname) + pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) + remaining_path = ltruncate(filename, max(1, widthfile - textwidth(pkgname) - 1)) + linenum = li.line == -1 ? "?" : string(li.line) + slash = (!isempty(pkgname) && !startswith(remaining_path, "/")) ? "/" : "" + styled_path = styled"{$pkgcolor:$pkgname}$slash$remaining_path:$linenum" + rich_file = if isempty(path) + styled_path + else + link = editor_link(path, li.line) + styled"{link=$link:$styled_path}" + end + strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, " ", fname) + if frame.overhead > 0 + strs[i] = styled"{bold:$(strs[i])}" + end end else strs[i] = string(stroverhead, "╎", base, strcount, " [unknown stackframe]") end - strs[i] = ltruncto(strs[i], cols) + strs[i] = rtruncate(strs[i], cols) end return strs end @@ -1101,10 +1157,10 @@ end # avoid stack overflows. function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat, is_subsection::Bool) where T maxes = maxstats(bt) - filenamemap = Dict{Symbol,String}() - worklist = [(bt, 0, 0, "")] + filenamemap = FileNameMap() + worklist = [(bt, 0, 0, AnnotatedString(""))] if !is_subsection - Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") + Base.print(io, "Overhead ╎ [+additional indent] Count File:Line Function\n") Base.print(io, "=========================================================\n") end while !isempty(worklist) @@ -1135,7 +1191,7 @@ function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat count = down.count count < fmt.mincount && continue count < noisefloor && continue - str = strs[i] + str = strs[i]::AnnotatedString noisefloor_down = fmt.noisefloor > 0 ? floor(Int, fmt.noisefloor * sqrt(count)) : 0 pushfirst!(worklist, (down, level + 1, noisefloor_down, str)) end @@ -1196,24 +1252,7 @@ function callersf(matchfunc::Function, bt::Vector, lidict::LineInfoFlatDict) return [(v[i], k[i]) for i in p] end -# Utilities -function rtruncto(str::String, w::Int) - if textwidth(str) <= w - return str - else - return string("…", str[prevind(str, end, w-2):end]) - end -end -function ltruncto(str::String, w::Int) - if textwidth(str) <= w - return str - else - return string(str[1:nextind(str, 1, w-2)], "…") - end -end - - -truncto(str::Symbol, w::Int) = truncto(string(str), w) +## Utilities # Order alphabetically (file, function) and then by line number function liperm(lilist::Vector{StackFrame}) From b1b968e9bdbaa9939c0d45f9f9eb437b2aa3a7fc Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Sep 2024 22:14:41 -0400 Subject: [PATCH 81/94] better signal handling (#55623) Instead of relying on creating a fake stack frame, and having no signals delivered, kernel bugs, accidentally gc_collect, or other issues occur during the delivery and execution of these calls, use the ability we added recently to emulate a longjmp into a unw_context to eliminate any time where there would exist any invalid states. Secondly, when calling jl_exit_thread0_cb, we used to end up completely smashing the unwind info (with CFI_NOUNWIND), but this makes core files from SIGQUIT much less helpful, so we now have a `fake_stack_pop` function with contains the necessary CFI directives such that a minimal unwind from the debugger will likely still succeed up into the frames that were removed. We cannot do this perfectly on AArch64 since that platform's DWARF spec lacks the ability to do so. On other platforms, this should be possible to implement exactly (subject to libunwind implementation quality). This is currently thus only fully implemented for x86_64 on Darwin Apple. --- src/jl_exported_funcs.inc | 1 - src/julia.h | 1 - src/julia_threads.h | 3 + src/rtutils.c | 27 ++- src/signals-mach.c | 128 +++++++--- src/signals-unix.c | 113 ++++----- src/signals-win.c | 76 +++--- src/stackwalk.c | 493 +++++++++++++++++++++----------------- src/task.c | 79 ++---- src/threading.c | 10 + 10 files changed, 519 insertions(+), 412 deletions(-) diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 11cc8ee6fddd9..7f1636ad9ad80 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -420,7 +420,6 @@ XX(jl_set_zero_subnormals) \ XX(jl_sigatomic_begin) \ XX(jl_sigatomic_end) \ - XX(jl_sig_throw) \ XX(jl_spawn) \ XX(jl_specializations_get_linfo) \ XX(jl_specializations_lookup) \ diff --git a/src/julia.h b/src/julia.h index caa938ffeb0d6..589a5745ff59f 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2310,7 +2310,6 @@ JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int16_t tid) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_set_task_threadpoolid(jl_task_t *task, int8_t tpid) JL_NOTSAFEPOINT; JL_DLLEXPORT void JL_NORETURN jl_throw(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_rethrow(void); -JL_DLLEXPORT void JL_NORETURN jl_sig_throw(void); JL_DLLEXPORT void JL_NORETURN jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e, jl_task_t *ct); JL_DLLEXPORT JL_CONST_FUNC jl_gcframe_t **(jl_get_pgcstack)(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; diff --git a/src/julia_threads.h b/src/julia_threads.h index e56ff5edd6176..7c6de1896ca13 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -187,6 +187,9 @@ typedef struct _jl_tls_states_t { // Saved exception for previous *external* API call or NULL if cleared. // Access via jl_exception_occurred(). struct _jl_value_t *previous_exception; +#ifdef _OS_DARWIN_ + jl_jmp_buf *volatile safe_restore; +#endif // currently-held locks, to be released when an exception is thrown small_arraylist_t locks; diff --git a/src/rtutils.c b/src/rtutils.c index a6a7fd5614de0..85a9be5e0b1da 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -269,10 +269,11 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh) // `eh` may be not equal to `ct->eh`. See `jl_pop_handler` // This function should **NOT** have any safepoint before the ones at the // end. - sig_atomic_t old_defer_signal = ct->ptls->defer_signal; + jl_ptls_t ptls = ct->ptls; + sig_atomic_t old_defer_signal = ptls->defer_signal; ct->eh = eh->prev; ct->gcstack = eh->gcstack; - small_arraylist_t *locks = &ct->ptls->locks; + small_arraylist_t *locks = &ptls->locks; int unlocks = locks->len > eh->locks_len; if (unlocks) { for (size_t i = locks->len; i > eh->locks_len; i--) @@ -280,14 +281,26 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh) locks->len = eh->locks_len; } ct->world_age = eh->world_age; - ct->ptls->defer_signal = eh->defer_signal; - int8_t old_gc_state = jl_atomic_load_relaxed(&ct->ptls->gc_state); + ptls->defer_signal = eh->defer_signal; + int8_t old_gc_state = jl_atomic_load_relaxed(&ptls->gc_state); if (old_gc_state != eh->gc_state) - jl_atomic_store_release(&ct->ptls->gc_state, eh->gc_state); + jl_atomic_store_release(&ptls->gc_state, eh->gc_state); if (!old_gc_state || !eh->gc_state) // it was or is unsafe now - jl_gc_safepoint_(ct->ptls); + jl_gc_safepoint_(ptls); + jl_value_t *exception = ptls->sig_exception; + if (exception) { + int8_t oldstate = jl_gc_unsafe_enter(ptls); + /* The temporary ptls->bt_data is rooted by special purpose code in the + GC. This exists only for the purpose of preserving bt_data until we + set ptls->bt_size=0 below. */ + jl_push_excstack(ct, &ct->excstack, exception, + ptls->bt_data, ptls->bt_size); + ptls->bt_size = 0; + ptls->sig_exception = NULL; + jl_gc_unsafe_leave(ptls, oldstate); + } if (old_defer_signal && !eh->defer_signal) - jl_sigint_safepoint(ct->ptls); + jl_sigint_safepoint(ptls); if (jl_atomic_load_relaxed(&jl_gc_have_pending_finalizers) && unlocks && eh->locks_len == 0) { jl_gc_run_pending_finalizers(ct); diff --git a/src/signals-mach.c b/src/signals-mach.c index 2f3e87ece296f..a939e4df71ae0 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -222,38 +222,92 @@ typedef arm_exception_state64_t host_exception_state_t; #define HOST_EXCEPTION_STATE_COUNT ARM_EXCEPTION_STATE64_COUNT #endif -static void jl_call_in_state(jl_ptls_t ptls2, host_thread_state_t *state, - void (*fptr)(void)) +// create a fake function that describes the variable manipulations in jl_call_in_state +__attribute__((naked)) static void fake_stack_pop(void) { #ifdef _CPU_X86_64_ - uintptr_t rsp = state->__rsp; + __asm__ volatile ( + " .cfi_signal_frame\n" + " .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly + " .cfi_offset %rip, 0\n" // previous value of %rip at CFA + " .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA + " nop\n" + ); #elif defined(_CPU_AARCH64_) - uintptr_t rsp = state->__sp; + __asm__ volatile ( + " .cfi_signal_frame\n" + " .cfi_def_cfa sp, 0\n" // use sp as fp here + " .cfi_offset lr, 0\n" + " .cfi_offset sp, 8\n" + // Anything else got smashed, since we didn't explicitly copy all of the + // state object to the stack (to build a real sigreturn frame). + // This is also not quite valid, since the AArch64 DWARF spec lacks the ability to define how to restore the LR register correctly, + // so normally libunwind implementations on linux detect this function specially and hack around the invalid info: + // https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958 + " nop\n" + ); #else -#error "julia: throw-in-context not supported on this platform" +CFI_NORETURN #endif - if (ptls2 == NULL || is_addr_on_sigstack(ptls2, (void*)rsp)) { - rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment - } - else { - rsp = (uintptr_t)ptls2->signal_stack + (ptls2->signal_stack_size ? ptls2->signal_stack_size : sig_stack_size); - } - assert(rsp % 16 == 0); - rsp -= 16; +} +static void jl_call_in_state(host_thread_state_t *state, void (*fptr)(void)) +{ #ifdef _CPU_X86_64_ - rsp -= sizeof(void*); - state->__rsp = rsp; // set stack pointer + uintptr_t sp = state->__rsp; +#elif defined(_CPU_AARCH64_) + uintptr_t sp = state->__sp; +#endif + sp = (sp - 256) & ~(uintptr_t)15; // redzone and re-alignment + assert(sp % 16 == 0); + sp -= 16; +#ifdef _CPU_X86_64_ + // set return address to NULL + *(uintptr_t*)sp = 0; + // pushq %sp + sp -= sizeof(void*); + *(uintptr_t*)sp = state->__rsp; + // pushq %rip + sp -= sizeof(void*); + *(uintptr_t*)sp = state->__rip; + // pushq .fake_stack_pop + 1; aka call from fake_stack_pop + sp -= sizeof(void*); + *(uintptr_t*)sp = (uintptr_t)&fake_stack_pop + 1; + state->__rsp = sp; // set stack pointer state->__rip = (uint64_t)fptr; // "call" the function #elif defined(_CPU_AARCH64_) - state->__sp = rsp; - state->__pc = (uint64_t)fptr; - state->__lr = 0; + // push {%sp, %pc + 4} + sp -= sizeof(void*); + *(uintptr_t*)sp = state->__sp; + sp -= sizeof(void*); + *(uintptr_t*)sp = (uintptr_t)state->__pc; + state->__sp = sp; // x31 + state->__pc = (uint64_t)fptr; // pc + state->__lr = (uintptr_t)&fake_stack_pop + 4; // x30 #else #error "julia: throw-in-context not supported on this platform" #endif } +static void jl_longjmp_in_state(host_thread_state_t *state, jl_jmp_buf jmpbuf) +{ + + if (!jl_simulate_longjmp(jmpbuf, (bt_context_t*)state)) { + // for sanitizer builds, fallback to calling longjmp on the original stack + // (this will fail for stack overflow, but that is hardly sanitizer-legal anyways) +#ifdef _CPU_X86_64_ + state->__rdi = (uintptr_t)jmpbuf; + state->__rsi = 1; +#elif defined(_CPU_AARCH64_) + state->__x[0] = (uintptr_t)jmpbuf; + state->__x[1] = 1; +#else +#error "julia: jl_longjmp_in_state not supported on this platform" +#endif + jl_call_in_state(state, (void (*)(void))longjmp); + } +} + #ifdef _CPU_X86_64_ int is_write_fault(host_exception_state_t exc_state) { return exc_reg_is_write_fault(exc_state.__err); @@ -275,14 +329,26 @@ static void jl_throw_in_thread(jl_ptls_t ptls2, mach_port_t thread, jl_value_t * host_thread_state_t state; kern_return_t ret = thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, &count); HANDLE_MACH_ERROR("thread_get_state", ret); - if (1) { // XXX: !jl_has_safe_restore(ptls2) + if (ptls2->safe_restore) { + jl_longjmp_in_state(&state, *ptls2->safe_restore); + } + else { assert(exception); ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, (bt_context_t *)&state, - NULL /*current_task?*/); + NULL /*current_task?*/); ptls2->sig_exception = exception; + ptls2->io_wait = 0; + jl_task_t *ct = ptls2->current_task; + jl_handler_t *eh = ct->eh; + if (eh != NULL) { + asan_unpoison_task_stack(ct, &eh->eh_ctx); + jl_longjmp_in_state(&state, eh->eh_ctx); + } + else { + jl_no_exc_handler(exception, ct); + } } - jl_call_in_state(ptls2, &state, &jl_sig_throw); ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state", ret); } @@ -290,10 +356,9 @@ static void jl_throw_in_thread(jl_ptls_t ptls2, mach_port_t thread, jl_value_t * static void segv_handler(int sig, siginfo_t *info, void *context) { assert(sig == SIGSEGV || sig == SIGBUS); - if (jl_get_safe_restore()) { // restarting jl_ or jl_unwind_stepn - jl_task_t *ct = jl_get_current_task(); - jl_ptls_t ptls = ct == NULL ? NULL : ct->ptls; - jl_call_in_state(ptls, (host_thread_state_t*)jl_to_bt_context(context), &jl_sig_throw); + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) { // restarting jl_ or jl_unwind_stepn + jl_longjmp_in_state((host_thread_state_t*)jl_to_bt_context(context), *saferestore); return; } jl_task_t *ct = jl_get_current_task(); @@ -354,12 +419,10 @@ kern_return_t catch_mach_exception_raise( jl_safe_printf("ERROR: Exception handler triggered on unmanaged thread.\n"); return KERN_INVALID_ARGUMENT; } - // XXX: jl_throw_in_thread or segv_handler will eventually check this, but - // we would like to avoid some of this work if we could detect this earlier - // if (jl_has_safe_restore(ptls2)) { - // jl_throw_in_thread(ptls2, thread, NULL); - // return KERN_SUCCESS; - // } + if (ptls2->safe_restore) { + jl_throw_in_thread(ptls2, thread, NULL); + return KERN_SUCCESS; + } if (jl_atomic_load_acquire(&ptls2->gc_state) == JL_GC_STATE_WAITING) return KERN_FAILURE; if (exception == EXC_ARITHMETIC) { @@ -518,7 +581,6 @@ static void jl_try_deliver_sigint(void) static void JL_NORETURN jl_exit_thread0_cb(int signo) { -CFI_NORETURN jl_critical_error(signo, 0, NULL, jl_current_task); jl_atexit_hook(128); jl_raise(signo); @@ -550,7 +612,7 @@ static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) #else #error Fill in first integer argument here #endif - jl_call_in_state(ptls2, &state, (void (*)(void))&jl_exit_thread0_cb); + jl_call_in_state(&state, (void (*)(void))&jl_exit_thread0_cb); unsigned int count = MACH_THREAD_STATE_COUNT; ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state", ret); diff --git a/src/signals-unix.c b/src/signals-unix.c index d719ac7fa452d..d0885b6bdee3f 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -44,7 +44,7 @@ static const size_t sig_stack_size = 8 * 1024 * 1024; // helper function for returning the unw_context_t inside a ucontext_t // (also used by stackwalk.c) -bt_context_t *jl_to_bt_context(void *sigctx) +bt_context_t *jl_to_bt_context(void *sigctx) JL_NOTSAFEPOINT { #ifdef __APPLE__ return (bt_context_t*)&((ucontext64_t*)sigctx)->uc_mcontext64->__ss; @@ -62,7 +62,11 @@ bt_context_t *jl_to_bt_context(void *sigctx) static int thread0_exit_count = 0; static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size); -static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void *_ctx) +int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT; +static void jl_longjmp_in_ctx(int sig, void *_ctx, jl_jmp_buf jmpbuf); + +#if !defined(_OS_DARWIN_) +static inline uintptr_t jl_get_rsp_from_ctx(const void *_ctx) { #if defined(_OS_LINUX_) && defined(_CPU_X86_64_) const ucontext_t *ctx = (const ucontext_t*)_ctx; @@ -76,12 +80,6 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * #elif defined(_OS_LINUX_) && defined(_CPU_ARM_) const ucontext_t *ctx = (const ucontext_t*)_ctx; return ctx->uc_mcontext.arm_sp; -#elif defined(_OS_DARWIN_) && defined(_CPU_X86_64_) - const ucontext64_t *ctx = (const ucontext64_t*)_ctx; - return ctx->uc_mcontext64->__ss.__rsp; -#elif defined(_OS_DARWIN_) && defined(_CPU_AARCH64_) - const ucontext64_t *ctx = (const ucontext64_t*)_ctx; - return ctx->uc_mcontext64->__ss.__sp; #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) const ucontext_t *ctx = (const ucontext_t*)_ctx; return ctx->uc_mcontext.mc_rsp; @@ -97,7 +95,7 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * #endif } -static int is_addr_on_sigstack(jl_ptls_t ptls, void *ptr) +static int is_addr_on_sigstack(jl_ptls_t ptls, void *ptr) JL_NOTSAFEPOINT { // One guard page for signal_stack. return ptls->signal_stack == NULL || @@ -105,10 +103,8 @@ static int is_addr_on_sigstack(jl_ptls_t ptls, void *ptr) (char*)ptr <= (char*)ptls->signal_stack + (ptls->signal_stack_size ? ptls->signal_stack_size : sig_stack_size)); } -// Modify signal context `_ctx` so that `fptr` will execute when the signal -// returns. `fptr` will execute on the signal stack, and must not return. -// jl_call_in_ctx is also currently executing on that signal stack, -// so be careful not to smash it +// Modify signal context `_ctx` so that `fptr` will execute when the signal returns +// The function `fptr` itself must not return. JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_ctx) { // Modifying the ucontext should work but there is concern that @@ -118,44 +114,36 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si // checks that the syscall is made in the signal handler and that // the ucontext address is valid. Hopefully the value of the ucontext // will not be part of the validation... - if (!ptls) { - sigset_t sset; - sigemptyset(&sset); - sigaddset(&sset, sig); - pthread_sigmask(SIG_UNBLOCK, &sset, NULL); - fptr(); - return; - } uintptr_t rsp = jl_get_rsp_from_ctx(_ctx); - if (is_addr_on_sigstack(ptls, (void*)rsp)) - rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment - else - rsp = (uintptr_t)ptls->signal_stack + (ptls->signal_stack_size ? ptls->signal_stack_size : sig_stack_size); - assert(rsp % 16 == 0); - rsp -= 16; + rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment #if defined(_OS_LINUX_) && defined(_CPU_X86_64_) ucontext_t *ctx = (ucontext_t*)_ctx; rsp -= sizeof(void*); + *(uintptr_t*)rsp = 0; ctx->uc_mcontext.gregs[REG_RSP] = rsp; ctx->uc_mcontext.gregs[REG_RIP] = (uintptr_t)fptr; #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) ucontext_t *ctx = (ucontext_t*)_ctx; rsp -= sizeof(void*); + *(uintptr_t*)rsp = 0; ctx->uc_mcontext.mc_rsp = rsp; ctx->uc_mcontext.mc_rip = (uintptr_t)fptr; #elif defined(_OS_LINUX_) && defined(_CPU_X86_) ucontext_t *ctx = (ucontext_t*)_ctx; rsp -= sizeof(void*); + *(uintptr_t*)rsp = 0; ctx->uc_mcontext.gregs[REG_ESP] = rsp; ctx->uc_mcontext.gregs[REG_EIP] = (uintptr_t)fptr; #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_) ucontext_t *ctx = (ucontext_t*)_ctx; rsp -= sizeof(void*); + *(uintptr_t*)rsp = 0; ctx->uc_mcontext.mc_esp = rsp; ctx->uc_mcontext.mc_eip = (uintptr_t)fptr; #elif defined(_OS_OPENBSD_) && defined(_CPU_X86_64_) struct sigcontext *ctx = (struct sigcontext *)_ctx; rsp -= sizeof(void*); + *(uintptr_t*)rsp = 0; ctx->sc_rsp = rsp; ctx->sc_rip = fptr; #elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_) @@ -187,22 +175,6 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si ctx->uc_mcontext.arm_sp = rsp; ctx->uc_mcontext.arm_lr = 0; // Clear link register ctx->uc_mcontext.arm_pc = target; -#elif defined(_OS_DARWIN_) && (defined(_CPU_X86_64_) || defined(_CPU_AARCH64_)) - // Only used for SIGFPE. - // This doesn't seems to be reliable when the SIGFPE is generated - // from a divide-by-zero exception, which is now handled by - // `catch_exception_raise`. It works fine when a signal is received - // due to `kill`/`raise` though. - ucontext64_t *ctx = (ucontext64_t*)_ctx; -#if defined(_CPU_X86_64_) - rsp -= sizeof(void*); - ctx->uc_mcontext64->__ss.__rsp = rsp; - ctx->uc_mcontext64->__ss.__rip = (uintptr_t)fptr; -#else - ctx->uc_mcontext64->__ss.__sp = rsp; - ctx->uc_mcontext64->__ss.__pc = (uintptr_t)fptr; - ctx->uc_mcontext64->__ss.__lr = 0; -#endif #else #pragma message("julia: throw-in-context not supported on this platform") // TODO Add support for PowerPC(64)? @@ -213,22 +185,30 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si fptr(); #endif } +#endif static void jl_throw_in_ctx(jl_task_t *ct, jl_value_t *e, int sig, void *sigctx) { jl_ptls_t ptls = ct->ptls; - if (!jl_get_safe_restore()) { - ptls->bt_size = - rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, jl_to_bt_context(sigctx), - ct->gcstack); - ptls->sig_exception = e; + assert(!jl_get_safe_restore()); + ptls->bt_size = + rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, jl_to_bt_context(sigctx), + ct->gcstack); + ptls->sig_exception = e; + ptls->io_wait = 0; + jl_handler_t *eh = ct->eh; + if (eh != NULL) { + asan_unpoison_task_stack(ct, &eh->eh_ctx); + jl_longjmp_in_ctx(sig, sigctx, eh->eh_ctx); + } + else { + jl_no_exc_handler(e, ct); } - jl_call_in_ctx(ptls, &jl_sig_throw, sig, sigctx); } static pthread_t signals_thread; -static int is_addr_on_stack(jl_task_t *ct, void *addr) +static int is_addr_on_stack(jl_task_t *ct, void *addr) JL_NOTSAFEPOINT { if (ct->ctx.copy_stack) { jl_ptls_t ptls = ct->ptls; @@ -379,7 +359,7 @@ int is_write_fault(void *context) { } #endif -static int jl_is_on_sigstack(jl_ptls_t ptls, void *ptr, void *context) +static int jl_is_on_sigstack(jl_ptls_t ptls, void *ptr, void *context) JL_NOTSAFEPOINT { return (ptls->signal_stack != NULL && is_addr_on_sigstack(ptls, ptr) && @@ -389,8 +369,9 @@ static int jl_is_on_sigstack(jl_ptls_t ptls, void *ptr, void *context) JL_NO_ASAN static void segv_handler(int sig, siginfo_t *info, void *context) { assert(sig == SIGSEGV || sig == SIGBUS); - if (jl_get_safe_restore()) { // restarting jl_ or profile - jl_call_in_ctx(NULL, &jl_sig_throw, sig, context); + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) { // restarting jl_ or profile + jl_longjmp_in_ctx(sig, context, *saferestore); return; } jl_task_t *ct = jl_get_current_task(); @@ -630,7 +611,11 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) jl_safe_printf("WARNING: Force throwing a SIGINT\n"); // Force a throw jl_clear_force_sigint(); - jl_throw_in_ctx(ct, jl_interrupt_exception, sig, ctx); + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) // restarting jl_ or profile + jl_longjmp_in_ctx(sig, ctx, *saferestore); + else + jl_throw_in_ctx(ct, jl_interrupt_exception, sig, ctx); } } else if (request == 3) { @@ -1100,8 +1085,9 @@ void restore_signals(void) static void fpe_handler(int sig, siginfo_t *info, void *context) { (void)info; - if (jl_get_safe_restore()) { // restarting jl_ or profile - jl_call_in_ctx(NULL, &jl_sig_throw, sig, context); + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) { // restarting jl_ or profile + jl_longjmp_in_ctx(sig, context, *saferestore); return; } jl_task_t *ct = jl_get_current_task(); @@ -1111,6 +1097,21 @@ static void fpe_handler(int sig, siginfo_t *info, void *context) jl_throw_in_ctx(ct, jl_diverror_exception, sig, context); } +static void jl_longjmp_in_ctx(int sig, void *_ctx, jl_jmp_buf jmpbuf) +{ +#if defined(_OS_DARWIN_) + jl_longjmp_in_state((host_thread_state_t*)jl_to_bt_context(_ctx), jmpbuf); +#else + if (jl_simulate_longjmp(jmpbuf, jl_to_bt_context(_ctx))) + return; + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, sig); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); + jl_longjmp(jmpbuf, 1); +#endif +} + static void sigint_handler(int sig) { jl_sigint_passed = 1; diff --git a/src/signals-win.c b/src/signals-win.c index dbc3f8dad0968..b5f8dd8bd79d9 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -86,9 +86,13 @@ void __cdecl crt_sig_handler(int sig, int num) } break; default: // SIGSEGV, SIGTERM, SIGILL, SIGABRT - if (sig == SIGSEGV && jl_get_safe_restore()) { - signal(sig, (void (__cdecl *)(int))crt_sig_handler); - jl_sig_throw(); + if (sig == SIGSEGV) { // restarting jl_ or profile + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) { + signal(sig, (void (__cdecl *)(int))crt_sig_handler); + jl_longjmp(*saferestore, 1); + return; + } } memset(&Context, 0, sizeof(Context)); RtlCaptureContext(&Context); @@ -126,41 +130,41 @@ void restore_signals(void) SetConsoleCtrlHandler(NULL, 0); } -void jl_throw_in_ctx(jl_task_t *ct, jl_value_t *excpt, PCONTEXT ctxThread) +int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c); + +static void jl_throw_in_ctx(jl_task_t *ct, jl_value_t *excpt, PCONTEXT ctxThread) { -#if defined(_CPU_X86_64_) - DWORD64 Rsp = (ctxThread->Rsp & (DWORD64)-16) - 8; -#elif defined(_CPU_X86_) - DWORD32 Esp = (ctxThread->Esp & (DWORD32)-16) - 4; -#else -#error WIN16 not supported :P -#endif - if (ct && !jl_get_safe_restore()) { - assert(excpt != NULL); - jl_ptls_t ptls = ct->ptls; - ptls->bt_size = 0; - if (excpt != jl_stackovf_exception) { - ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, ctxThread, - ct->gcstack); - } - else if (have_backtrace_fiber) { - uv_mutex_lock(&backtrace_lock); - stkerror_ctx = ctxThread; - stkerror_ptls = ptls; - jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber); - uv_mutex_unlock(&backtrace_lock); - } - ptls->sig_exception = excpt; + jl_jmp_buf *saferestore = jl_get_safe_restore(); + if (saferestore) { // restarting jl_ or profile + if (!jl_simulate_longjmp(*saferestore, ctxThread)) + abort(); + return; + } + assert(ct && excpt); + jl_ptls_t ptls = ct->ptls; + ptls->bt_size = 0; + if (excpt != jl_stackovf_exception) { + ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, ctxThread, + ct->gcstack); + } + else if (have_backtrace_fiber) { + uv_mutex_lock(&backtrace_lock); + stkerror_ctx = ctxThread; + stkerror_ptls = ptls; + jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber); + uv_mutex_unlock(&backtrace_lock); + } + ptls->sig_exception = excpt; + ptls->io_wait = 0; + jl_handler_t *eh = ct->eh; + if (eh != NULL) { + asan_unpoison_task_stack(ct, &eh->eh_ctx); + if (!jl_simulate_longjmp(eh->eh_ctx, ctxThread)) + abort(); + } + else { + jl_no_exc_handler(excpt, ct); } -#if defined(_CPU_X86_64_) - *(DWORD64*)Rsp = 0; - ctxThread->Rsp = Rsp; - ctxThread->Rip = (DWORD64)&jl_sig_throw; -#elif defined(_CPU_X86_) - *(DWORD32*)Esp = 0; - ctxThread->Esp = Esp; - ctxThread->Eip = (DWORD)&jl_sig_throw; -#endif } HANDLE hMainThread = INVALID_HANDLE_VALUE; diff --git a/src/stackwalk.c b/src/stackwalk.c index 15a9fddeac9a4..6aa36fa8b499c 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -919,7 +919,273 @@ _os_ptr_munge(uintptr_t ptr) JL_NOTSAFEPOINT #endif -extern bt_context_t *jl_to_bt_context(void *sigctx); +extern bt_context_t *jl_to_bt_context(void *sigctx) JL_NOTSAFEPOINT; + +// Some notes: this simulates a longjmp call occurring in context `c`, as if the +// user was to set the PC in `c` to call longjmp and the PC in the longjmp to +// return here. This helps work around many cases where siglongjmp out of a +// signal handler is not supported (e.g. missing a _sigunaltstack call). +// Additionally note that this doesn't restore the MXCSR or FP control word +// (which some, but not most longjmp implementations do). It also doesn't +// support shadow stacks, so if those are in use, you might need to use a direct +// jl_longjmp instead to leave the signal frame instead of relying on simulating +// it and attempting to return normally. +int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT +{ +#if (defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_)) + https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/hwasan/hwasan_interceptors.cpp + return 0; +#elif defined(_OS_WINDOWS_) + _JUMP_BUFFER* _ctx = (_JUMP_BUFFER*)mctx; + #if defined(_CPU_X86_64_) + c->Rbx = _ctx->Rbx; + c->Rsp = _ctx->Rsp; + c->Rbp = _ctx->Rbp; + c->Rsi = _ctx->Rsi; + c->Rdi = _ctx->Rdi; + c->R12 = _ctx->R12; + c->R13 = _ctx->R13; + c->R14 = _ctx->R14; + c->R15 = _ctx->R15; + c->Rip = _ctx->Rip; + memcpy(&c->Xmm6, &_ctx->Xmm6, 10 * sizeof(_ctx->Xmm6)); // Xmm6-Xmm15 + // c->MxCsr = _ctx->MxCsr; + // c->FloatSave.ControlWord = _ctx->FpCsr; + // c->SegGS[0] = _ctx->Frame; + c->Rax = 1; + c->Rsp += sizeof(void*); + assert(c->Rsp % 16 == 0); + return 1; + #elif defined(_CPU_X86_) + c->Ebp = _ctx->Ebp; + c->Ebx = _ctx->Ebx; + c->Edi = _ctx->Edi; + c->Esi = _ctx->Esi; + c->Esp = _ctx->Esp; + c->Eip = _ctx->Eip; + // c->SegFS[0] = _ctx->Registration; + // c->FloatSave.ControlWord = _ctx->FpCsr; + c->Eax = 1; + c->Esp += sizeof(void*); + assert(c->Esp % 16 == 0); + return 1; + #else + #error Windows is currently only supported on x86 and x86_64 + #endif +#elif defined(_OS_LINUX_) && defined(__GLIBC__) + __jmp_buf *_ctx = &mctx->__jmpbuf; + mcontext_t *mc = &c->uc_mcontext; + #if defined(_CPU_X86_) + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/i386/longjmp.s + mc->gregs[REG_EBX] = (*_ctx)[0]; + mc->gregs[REG_ESI] = (*_ctx)[1]; + mc->gregs[REG_EDI] = (*_ctx)[2]; + mc->gregs[REG_EBP] = (*_ctx)[3]; + mc->gregs[REG_ESP] = (*_ctx)[4]; + mc->gregs[REG_EIP] = (*_ctx)[5]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_ESP] = ptr_demangle(mc->gregs[REG_ESP]); + mc->gregs[REG_EIP] = ptr_demangle(mc->gregs[REG_EIP]); + mc->gregs[REG_EAX] = 1; + assert(mc->gregs[REG_ESP] % 16 == 0); + return 1; + #elif defined(_CPU_X86_64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/x86_64/setjmp.s + mc->gregs[REG_RBX] = (*_ctx)[0]; + mc->gregs[REG_RBP] = (*_ctx)[1]; + mc->gregs[REG_R12] = (*_ctx)[2]; + mc->gregs[REG_R13] = (*_ctx)[3]; + mc->gregs[REG_R14] = (*_ctx)[4]; + mc->gregs[REG_R15] = (*_ctx)[5]; + mc->gregs[REG_RSP] = (*_ctx)[6]; + mc->gregs[REG_RIP] = (*_ctx)[7]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_RBP] = ptr_demangle(mc->gregs[REG_RBP]); + mc->gregs[REG_RSP] = ptr_demangle(mc->gregs[REG_RSP]); + mc->gregs[REG_RIP] = ptr_demangle(mc->gregs[REG_RIP]); + mc->gregs[REG_RAX] = 1; + assert(mc->gregs[REG_RSP] % 16 == 0); + return 1; + #elif defined(_CPU_ARM_) + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/include/bits/setjmp.h + // https://github.com/bminor/musl/blame/master/src/setjmp/arm/longjmp.S + mc->arm_sp = (*_ctx)[0]; + mc->arm_lr = (*_ctx)[1]; + mc->arm_r4 = (*_ctx)[2]; // aka v1 + mc->arm_r5 = (*_ctx)[3]; // aka v2 + mc->arm_r6 = (*_ctx)[4]; // aka v3 + mc->arm_r7 = (*_ctx)[5]; // aka v4 + mc->arm_r8 = (*_ctx)[6]; // aka v5 + mc->arm_r9 = (*_ctx)[7]; // aka v6 aka sb + mc->arm_r10 = (*_ctx)[8]; // aka v7 aka sl + mc->arm_fp = (*_ctx)[10]; // aka v8 aka r11 + // ifdef PTR_DEMANGLE ? + mc->arm_sp = ptr_demangle(mc->arm_sp); + mc->arm_lr = ptr_demangle(mc->arm_lr); + mc->arm_pc = mc->arm_lr; + mc->arm_r0 = 1; + assert(mc->arm_sp % 16 == 0); + return 1; + #elif defined(_CPU_AARCH64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/aarch64/longjmp.s + // https://github.com/libunwind/libunwind/blob/ec171c9ba7ea3abb2a1383cee2988a7abd483a1f/src/aarch64/unwind_i.h#L62 + unw_fpsimd_context_t *mcfp = (unw_fpsimd_context_t*)&mc->__reserved; + mc->regs[19] = (*_ctx)[0]; + mc->regs[20] = (*_ctx)[1]; + mc->regs[21] = (*_ctx)[2]; + mc->regs[22] = (*_ctx)[3]; + mc->regs[23] = (*_ctx)[4]; + mc->regs[24] = (*_ctx)[5]; + mc->regs[25] = (*_ctx)[6]; + mc->regs[26] = (*_ctx)[7]; + mc->regs[27] = (*_ctx)[8]; + mc->regs[28] = (*_ctx)[9]; + mc->regs[29] = (*_ctx)[10]; // aka fp + mc->regs[30] = (*_ctx)[11]; // aka lr + // Yes, they did skip 12 why writing the code originally; and, no, I do not know why. + mc->sp = (*_ctx)[13]; + mcfp->vregs[7] = (*_ctx)[14]; // aka d8 + mcfp->vregs[8] = (*_ctx)[15]; // aka d9 + mcfp->vregs[9] = (*_ctx)[16]; // aka d10 + mcfp->vregs[10] = (*_ctx)[17]; // aka d11 + mcfp->vregs[11] = (*_ctx)[18]; // aka d12 + mcfp->vregs[12] = (*_ctx)[19]; // aka d13 + mcfp->vregs[13] = (*_ctx)[20]; // aka d14 + mcfp->vregs[14] = (*_ctx)[21]; // aka d15 + // ifdef PTR_DEMANGLE ? + mc->sp = ptr_demangle(mc->sp); + mc->regs[30] = ptr_demangle(mc->regs[30]); + mc->pc = mc->regs[30]; + mc->regs[0] = 1; + assert(mc->sp % 16 == 0); + return 1; + #else + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown linux") + (void)mc; + (void)mctx; + return 0; + #endif +#elif defined(_OS_DARWIN_) + #if defined(_CPU_X86_64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s + x86_thread_state64_t *mc = (x86_thread_state64_t*)c; + mc->__rbx = ((uint64_t*)mctx)[0]; + mc->__rbp = ((uint64_t*)mctx)[1]; + mc->__rsp = ((uint64_t*)mctx)[2]; + mc->__r12 = ((uint64_t*)mctx)[3]; + mc->__r13 = ((uint64_t*)mctx)[4]; + mc->__r14 = ((uint64_t*)mctx)[5]; + mc->__r15 = ((uint64_t*)mctx)[6]; + mc->__rip = ((uint64_t*)mctx)[7]; + // added in libsystem_platform 177.200.16 (macOS Mojave 10.14.3) + // prior to that _os_ptr_munge_token was (hopefully) typically 0, + // so x ^ 0 == x and this is a no-op + mc->__rbp = _OS_PTR_UNMUNGE(mc->__rbp); + mc->__rsp = _OS_PTR_UNMUNGE(mc->__rsp); + mc->__rip = _OS_PTR_UNMUNGE(mc->__rip); + mc->__rax = 1; + assert(mc->__rsp % 16 == 0); + return 1; + #elif defined(_CPU_AARCH64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/arm64/setjmp.s + // https://github.com/apple/darwin-xnu/blob/main/osfmk/mach/arm/_structs.h + // https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/libunwind/src/Registers.hpp -> Registers_arm64 + arm_thread_state64_t *mc = (arm_thread_state64_t*)c; + mc->__x[19] = ((uint64_t*)mctx)[0]; + mc->__x[20] = ((uint64_t*)mctx)[1]; + mc->__x[21] = ((uint64_t*)mctx)[2]; + mc->__x[22] = ((uint64_t*)mctx)[3]; + mc->__x[23] = ((uint64_t*)mctx)[4]; + mc->__x[24] = ((uint64_t*)mctx)[5]; + mc->__x[25] = ((uint64_t*)mctx)[6]; + mc->__x[26] = ((uint64_t*)mctx)[7]; + mc->__x[27] = ((uint64_t*)mctx)[8]; + mc->__x[28] = ((uint64_t*)mctx)[9]; + mc->__x[10] = ((uint64_t*)mctx)[10]; + mc->__x[11] = ((uint64_t*)mctx)[11]; + mc->__x[12] = ((uint64_t*)mctx)[12]; + // 13 is reserved/unused + double *mcfp = (double*)&mc[1]; + mcfp[7] = ((uint64_t*)mctx)[14]; // aka d8 + mcfp[8] = ((uint64_t*)mctx)[15]; // aka d9 + mcfp[9] = ((uint64_t*)mctx)[16]; // aka d10 + mcfp[10] = ((uint64_t*)mctx)[17]; // aka d11 + mcfp[11] = ((uint64_t*)mctx)[18]; // aka d12 + mcfp[12] = ((uint64_t*)mctx)[19]; // aka d13 + mcfp[13] = ((uint64_t*)mctx)[20]; // aka d14 + mcfp[14] = ((uint64_t*)mctx)[21]; // aka d15 + mc->__fp = _OS_PTR_UNMUNGE(mc->__x[10]); + mc->__lr = _OS_PTR_UNMUNGE(mc->__x[11]); + mc->__x[12] = _OS_PTR_UNMUNGE(mc->__x[12]); + mc->__sp = mc->__x[12]; + // libunwind is broken for signed-pointers, but perhaps best not to leave the signed pointer lying around either + mc->__pc = ptrauth_strip(mc->__lr, 0); + mc->__pad = 0; // aka __ra_sign_state = not signed + mc->__x[0] = 1; + assert(mc->__sp % 16 == 0); + return 1; + #else + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown darwin") + (void)mctx; + return 0; +#endif +#elif defined(_OS_FREEBSD_) + mcontext_t *mc = &c->uc_mcontext; + #if defined(_CPU_X86_64_) + // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S + mc->mc_rip = ((long*)mctx)[0]; + mc->mc_rbx = ((long*)mctx)[1]; + mc->mc_rsp = ((long*)mctx)[2]; + mc->mc_rbp = ((long*)mctx)[3]; + mc->mc_r12 = ((long*)mctx)[4]; + mc->mc_r13 = ((long*)mctx)[5]; + mc->mc_r14 = ((long*)mctx)[6]; + mc->mc_r15 = ((long*)mctx)[7]; + mc->mc_rax = 1; + mc->mc_rsp += sizeof(void*); + assert(mc->mc_rsp % 16 == 0); + return 1; + #elif defined(_CPU_AARCH64_) + mc->mc_gpregs.gp_x[19] = ((long*)mctx)[0]; + mc->mc_gpregs.gp_x[20] = ((long*)mctx)[1]; + mc->mc_gpregs.gp_x[21] = ((long*)mctx)[2]; + mc->mc_gpregs.gp_x[22] = ((long*)mctx)[3]; + mc->mc_gpregs.gp_x[23] = ((long*)mctx)[4]; + mc->mc_gpregs.gp_x[24] = ((long*)mctx)[5]; + mc->mc_gpregs.gp_x[25] = ((long*)mctx)[6]; + mc->mc_gpregs.gp_x[26] = ((long*)mctx)[7]; + mc->mc_gpregs.gp_x[27] = ((long*)mctx)[8]; + mc->mc_gpregs.gp_x[28] = ((long*)mctx)[9]; + mc->mc_gpregs.gp_x[29] = ((long*)mctx)[10]; + mc->mc_gpregs.gp_lr = ((long*)mctx)[11]; + mc->mc_gpregs.gp_sp = ((long*)mctx)[12]; + mc->mc_fpregs.fp_q[7] = ((long*)mctx)[13]; + mc->mc_fpregs.fp_q[8] = ((long*)mctx)[14]; + mc->mc_fpregs.fp_q[9] = ((long*)mctx)[15]; + mc->mc_fpregs.fp_q[10] = ((long*)mctx)[16]; + mc->mc_fpregs.fp_q[11] = ((long*)mctx)[17]; + mc->mc_fpregs.fp_q[12] = ((long*)mctx)[18]; + mc->mc_fpregs.fp_q[13] = ((long*)mctx)[19]; + mc->mc_fpregs.fp_q[14] = ((long*)mctx)[20]; + mc->mc_gpregs.gp_x[0] = 1; + assert(mc->mc_gpregs.gp_sp % 16 == 0); + return 1; + #else + #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown freebsd") + (void)mctx; + return 0; + #endif +#else +return 0; +#endif +} JL_DLLEXPORT size_t jl_record_backtrace(jl_task_t *t, jl_bt_element_t *bt_data, size_t max_bt_size) JL_NOTSAFEPOINT { @@ -955,234 +1221,19 @@ JL_DLLEXPORT size_t jl_record_backtrace(jl_task_t *t, jl_bt_element_t *bt_data, } if (context == NULL && (!t->ctx.copy_stack && t->ctx.started && t->ctx.ctx != NULL)) { // need to read the context from the task stored state + jl_jmp_buf *mctx = &t->ctx.ctx->uc_mcontext; #if defined(_OS_WINDOWS_) memset(&c, 0, sizeof(c)); - _JUMP_BUFFER *mctx = (_JUMP_BUFFER*)&t->ctx.ctx->uc_mcontext; -#if defined(_CPU_X86_64_) - c.Rbx = mctx->Rbx; - c.Rsp = mctx->Rsp; - c.Rbp = mctx->Rbp; - c.Rsi = mctx->Rsi; - c.Rdi = mctx->Rdi; - c.R12 = mctx->R12; - c.R13 = mctx->R13; - c.R14 = mctx->R14; - c.R15 = mctx->R15; - c.Rip = mctx->Rip; - memcpy(&c.Xmm6, &mctx->Xmm6, 10 * sizeof(mctx->Xmm6)); // Xmm6-Xmm15 -#elif defined(_CPU_X86_) - c.Eip = mctx->Eip; - c.Esp = mctx->Esp; - c.Ebp = mctx->Ebp; -#else - #error Windows is currently only supported on x86 and x86_64 -#endif - context = &c; + if (jl_simulate_longjmp(*mctx, &c)) + context = &c; #elif defined(JL_HAVE_UNW_CONTEXT) context = t->ctx.ctx; #elif defined(JL_HAVE_UCONTEXT) context = jl_to_bt_context(t->ctx.ctx); #elif defined(JL_HAVE_ASM) memset(&c, 0, sizeof(c)); - #if defined(_OS_LINUX_) && defined(__GLIBC__) - __jmp_buf *mctx = &t->ctx.ctx->uc_mcontext->__jmpbuf; - mcontext_t *mc = &c.uc_mcontext; - #if defined(_CPU_X86_) - // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/i386/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/i386/longjmp.s - mc->gregs[REG_EBX] = (*mctx)[0]; - mc->gregs[REG_ESI] = (*mctx)[1]; - mc->gregs[REG_EDI] = (*mctx)[2]; - mc->gregs[REG_EBP] = (*mctx)[3]; - mc->gregs[REG_ESP] = (*mctx)[4]; - mc->gregs[REG_EIP] = (*mctx)[5]; - // ifdef PTR_DEMANGLE ? - mc->gregs[REG_ESP] = ptr_demangle(mc->gregs[REG_ESP]); - mc->gregs[REG_EIP] = ptr_demangle(mc->gregs[REG_EIP]); - context = &c; - #elif defined(_CPU_X86_64_) - // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/x86_64/setjmp.s - mc->gregs[REG_RBX] = (*mctx)[0]; - mc->gregs[REG_RBP] = (*mctx)[1]; - mc->gregs[REG_R12] = (*mctx)[2]; - mc->gregs[REG_R13] = (*mctx)[3]; - mc->gregs[REG_R14] = (*mctx)[4]; - mc->gregs[REG_R15] = (*mctx)[5]; - mc->gregs[REG_RSP] = (*mctx)[6]; - mc->gregs[REG_RIP] = (*mctx)[7]; - // ifdef PTR_DEMANGLE ? - mc->gregs[REG_RBP] = ptr_demangle(mc->gregs[REG_RBP]); - mc->gregs[REG_RSP] = ptr_demangle(mc->gregs[REG_RSP]); - mc->gregs[REG_RIP] = ptr_demangle(mc->gregs[REG_RIP]); - context = &c; - #elif defined(_CPU_ARM_) - // https://github.com/bminor/glibc/blame/master/sysdeps/arm/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/arm/include/bits/setjmp.h - // https://github.com/bminor/musl/blame/master/src/setjmp/arm/longjmp.S - mc->arm_sp = (*mctx)[0]; - mc->arm_lr = (*mctx)[1]; - mc->arm_r4 = (*mctx)[2]; // aka v1 - mc->arm_r5 = (*mctx)[3]; // aka v2 - mc->arm_r6 = (*mctx)[4]; // aka v3 - mc->arm_r7 = (*mctx)[5]; // aka v4 - mc->arm_r8 = (*mctx)[6]; // aka v5 - mc->arm_r9 = (*mctx)[7]; // aka v6 aka sb - mc->arm_r10 = (*mctx)[8]; // aka v7 aka sl - mc->arm_fp = (*mctx)[10]; // aka v8 aka r11 - // ifdef PTR_DEMANGLE ? - mc->arm_sp = ptr_demangle(mc->arm_sp); - mc->arm_lr = ptr_demangle(mc->arm_lr); - mc->arm_pc = mc->arm_lr; - context = &c; - #elif defined(_CPU_AARCH64_) - // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/aarch64/longjmp.s - // https://github.com/libunwind/libunwind/blob/ec171c9ba7ea3abb2a1383cee2988a7abd483a1f/src/aarch64/unwind_i.h#L62 - unw_fpsimd_context_t *mcfp = (unw_fpsimd_context_t*)&mc->__reserved; - mc->regs[19] = (*mctx)[0]; - mc->regs[20] = (*mctx)[1]; - mc->regs[21] = (*mctx)[2]; - mc->regs[22] = (*mctx)[3]; - mc->regs[23] = (*mctx)[4]; - mc->regs[24] = (*mctx)[5]; - mc->regs[25] = (*mctx)[6]; - mc->regs[26] = (*mctx)[7]; - mc->regs[27] = (*mctx)[8]; - mc->regs[28] = (*mctx)[9]; - mc->regs[29] = (*mctx)[10]; // aka fp - mc->regs[30] = (*mctx)[11]; // aka lr - // Yes, they did skip 12 why writing the code originally; and, no, I do not know why. - mc->sp = (*mctx)[13]; - mcfp->vregs[7] = (*mctx)[14]; // aka d8 - mcfp->vregs[8] = (*mctx)[15]; // aka d9 - mcfp->vregs[9] = (*mctx)[16]; // aka d10 - mcfp->vregs[10] = (*mctx)[17]; // aka d11 - mcfp->vregs[11] = (*mctx)[18]; // aka d12 - mcfp->vregs[12] = (*mctx)[19]; // aka d13 - mcfp->vregs[13] = (*mctx)[20]; // aka d14 - mcfp->vregs[14] = (*mctx)[21]; // aka d15 - // ifdef PTR_DEMANGLE ? - mc->sp = ptr_demangle(mc->sp); - mc->regs[30] = ptr_demangle(mc->regs[30]); - mc->pc = mc->regs[30]; - context = &c; - #else - #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown linux") - (void)mc; - (void)c; - (void)mctx; - #endif - #elif defined(_OS_DARWIN_) - sigjmp_buf *mctx = &t->ctx.ctx->uc_mcontext; - #if defined(_CPU_X86_64_) - // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s - x86_thread_state64_t *mc = (x86_thread_state64_t*)&c; - mc->__rbx = ((uint64_t*)mctx)[0]; - mc->__rbp = ((uint64_t*)mctx)[1]; - mc->__rsp = ((uint64_t*)mctx)[2]; - mc->__r12 = ((uint64_t*)mctx)[3]; - mc->__r13 = ((uint64_t*)mctx)[4]; - mc->__r14 = ((uint64_t*)mctx)[5]; - mc->__r15 = ((uint64_t*)mctx)[6]; - mc->__rip = ((uint64_t*)mctx)[7]; - // added in libsystem_platform 177.200.16 (macOS Mojave 10.14.3) - // prior to that _os_ptr_munge_token was (hopefully) typically 0, - // so x ^ 0 == x and this is a no-op - mc->__rbp = _OS_PTR_UNMUNGE(mc->__rbp); - mc->__rsp = _OS_PTR_UNMUNGE(mc->__rsp); - mc->__rip = _OS_PTR_UNMUNGE(mc->__rip); - context = &c; - #elif defined(_CPU_AARCH64_) - // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/arm64/setjmp.s - // https://github.com/apple/darwin-xnu/blob/main/osfmk/mach/arm/_structs.h - // https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/libunwind/src/Registers.hpp -> Registers_arm64 - arm_thread_state64_t *mc = (arm_thread_state64_t*)&c; - mc->__x[19] = ((uint64_t*)mctx)[0]; - mc->__x[20] = ((uint64_t*)mctx)[1]; - mc->__x[21] = ((uint64_t*)mctx)[2]; - mc->__x[22] = ((uint64_t*)mctx)[3]; - mc->__x[23] = ((uint64_t*)mctx)[4]; - mc->__x[24] = ((uint64_t*)mctx)[5]; - mc->__x[25] = ((uint64_t*)mctx)[6]; - mc->__x[26] = ((uint64_t*)mctx)[7]; - mc->__x[27] = ((uint64_t*)mctx)[8]; - mc->__x[28] = ((uint64_t*)mctx)[9]; - mc->__x[10] = ((uint64_t*)mctx)[10]; - mc->__x[11] = ((uint64_t*)mctx)[11]; - mc->__x[12] = ((uint64_t*)mctx)[12]; - // 13 is reserved/unused - double *mcfp = (double*)&mc[1]; - mcfp[7] = ((uint64_t*)mctx)[14]; // aka d8 - mcfp[8] = ((uint64_t*)mctx)[15]; // aka d9 - mcfp[9] = ((uint64_t*)mctx)[16]; // aka d10 - mcfp[10] = ((uint64_t*)mctx)[17]; // aka d11 - mcfp[11] = ((uint64_t*)mctx)[18]; // aka d12 - mcfp[12] = ((uint64_t*)mctx)[19]; // aka d13 - mcfp[13] = ((uint64_t*)mctx)[20]; // aka d14 - mcfp[14] = ((uint64_t*)mctx)[21]; // aka d15 - mc->__fp = _OS_PTR_UNMUNGE(mc->__x[10]); - mc->__lr = _OS_PTR_UNMUNGE(mc->__x[11]); - mc->__x[12] = _OS_PTR_UNMUNGE(mc->__x[12]); - mc->__sp = mc->__x[12]; - // libunwind is broken for signed-pointers, but perhaps best not to leave the signed pointer lying around either - mc->__pc = ptrauth_strip(mc->__lr, 0); - mc->__pad = 0; // aka __ra_sign_state = not signed - context = &c; - #else - #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown darwin") - (void)mctx; - (void)c; - #endif - #elif defined(_OS_FREEBSD_) - sigjmp_buf *mctx = &t->ctx.ctx->uc_mcontext; - mcontext_t *mc = &c.uc_mcontext; - #if defined(_CPU_X86_64_) - // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S - mc->mc_rip = ((long*)mctx)[0]; - mc->mc_rbx = ((long*)mctx)[1]; - mc->mc_rsp = ((long*)mctx)[2]; - mc->mc_rbp = ((long*)mctx)[3]; - mc->mc_r12 = ((long*)mctx)[4]; - mc->mc_r13 = ((long*)mctx)[5]; - mc->mc_r14 = ((long*)mctx)[6]; - mc->mc_r15 = ((long*)mctx)[7]; - context = &c; - #elif defined(_CPU_AARCH64_) - mc->mc_gpregs.gp_x[19] = ((long*)mctx)[0]; - mc->mc_gpregs.gp_x[20] = ((long*)mctx)[1]; - mc->mc_gpregs.gp_x[21] = ((long*)mctx)[2]; - mc->mc_gpregs.gp_x[22] = ((long*)mctx)[3]; - mc->mc_gpregs.gp_x[23] = ((long*)mctx)[4]; - mc->mc_gpregs.gp_x[24] = ((long*)mctx)[5]; - mc->mc_gpregs.gp_x[25] = ((long*)mctx)[6]; - mc->mc_gpregs.gp_x[26] = ((long*)mctx)[7]; - mc->mc_gpregs.gp_x[27] = ((long*)mctx)[8]; - mc->mc_gpregs.gp_x[28] = ((long*)mctx)[9]; - mc->mc_gpregs.gp_x[29] = ((long*)mctx)[10]; - mc->mc_gpregs.gp_lr = ((long*)mctx)[11]; - mc->mc_gpregs.gp_sp = ((long*)mctx)[12]; - mc->mc_fpregs.fp_q[7] = ((long*)mctx)[13]; - mc->mc_fpregs.fp_q[8] = ((long*)mctx)[14]; - mc->mc_fpregs.fp_q[9] = ((long*)mctx)[15]; - mc->mc_fpregs.fp_q[10] = ((long*)mctx)[16]; - mc->mc_fpregs.fp_q[11] = ((long*)mctx)[17]; - mc->mc_fpregs.fp_q[12] = ((long*)mctx)[18]; - mc->mc_fpregs.fp_q[13] = ((long*)mctx)[19]; - mc->mc_fpregs.fp_q[14] = ((long*)mctx)[20]; - context = &c; - #else - #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown freebsd") - (void)mctx; - (void)c; - #endif - #else - #pragma message("jl_record_backtrace not defined for ASM/SETJMP on unknown system") - (void)c; - #endif + if (jl_simulate_longjmp(*mctx, &c)) + context = &c; #else #pragma message("jl_record_backtrace not defined for unknown task system") #endif diff --git a/src/task.c b/src/task.c index 86acac23a186a..f86e0ab3a880d 100644 --- a/src/task.c +++ b/src/task.c @@ -771,48 +771,31 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e, jl_task_t *ct) #define pop_timings_stack() /* Nothing */ #endif -#define throw_internal_body(altstack) \ - assert(!jl_get_safe_restore()); \ - jl_ptls_t ptls = ct->ptls; \ - ptls->io_wait = 0; \ - jl_gc_unsafe_enter(ptls); \ - if (exception) { \ - /* The temporary ptls->bt_data is rooted by special purpose code in the\ - GC. This exists only for the purpose of preserving bt_data until we \ - set ptls->bt_size=0 below. */ \ - jl_push_excstack(ct, &ct->excstack, exception, \ - ptls->bt_data, ptls->bt_size); \ - ptls->bt_size = 0; \ - } \ - assert(ct->excstack && ct->excstack->top); \ - jl_handler_t *eh = ct->eh; \ - if (eh != NULL) { \ - if (altstack) ptls->sig_exception = NULL; \ - pop_timings_stack() \ - asan_unpoison_task_stack(ct, &eh->eh_ctx); \ - jl_longjmp(eh->eh_ctx, 1); \ - } \ - else { \ - jl_no_exc_handler(exception, ct); \ - } \ - assert(0); - static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_MAYBE_UNROOTED) { -CFI_NORETURN JL_GC_PUSH1(&exception); - throw_internal_body(0); - jl_unreachable(); -} - -/* On the signal stack, we don't want to create any asan frames, but we do on the - normal, stack, so we split this function in two, depending on which context - we're calling it in. This also lets us avoid making a GC frame on the altstack, - which might end up getting corrupted if we recur here through another signal. */ -JL_NO_ASAN static void JL_NORETURN throw_internal_altstack(jl_task_t *ct, jl_value_t *exception) -{ -CFI_NORETURN - throw_internal_body(1); + jl_ptls_t ptls = ct->ptls; + ptls->io_wait = 0; + jl_gc_unsafe_enter(ptls); + if (exception) { + /* The temporary ptls->bt_data is rooted by special purpose code in the\ + GC. This exists only for the purpose of preserving bt_data until we + set ptls->bt_size=0 below. */ + jl_push_excstack(ct, &ct->excstack, exception, + ptls->bt_data, ptls->bt_size); + ptls->bt_size = 0; + } + assert(ct->excstack && ct->excstack->top); + jl_handler_t *eh = ct->eh; + if (eh != NULL) { + pop_timings_stack() + asan_unpoison_task_stack(ct, &eh->eh_ctx); + jl_longjmp(eh->eh_ctx, 1); + } + else { + jl_no_exc_handler(exception, ct); + } + assert(0); jl_unreachable(); } @@ -842,24 +825,6 @@ JL_DLLEXPORT void jl_rethrow(void) throw_internal(ct, NULL); } -// Special case throw for errors detected inside signal handlers. This is not -// (cannot be) called directly in the signal handler itself, but is returned to -// after the signal handler exits. -JL_DLLEXPORT JL_NO_ASAN void JL_NORETURN jl_sig_throw(void) -{ -CFI_NORETURN - jl_jmp_buf *safe_restore = jl_get_safe_restore(); - jl_task_t *ct = jl_current_task; - if (safe_restore) { - asan_unpoison_task_stack(ct, safe_restore); - jl_longjmp(*safe_restore, 1); - } - jl_ptls_t ptls = ct->ptls; - jl_value_t *e = ptls->sig_exception; - JL_GC_PROMISE_ROOTED(e); - throw_internal_altstack(ct, e); -} - JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) { // TODO: Should uses of `rethrow(exc)` be replaced with a normal throw, now diff --git a/src/threading.c b/src/threading.c index a6050ace01833..2f3719b89fac3 100644 --- a/src/threading.c +++ b/src/threading.c @@ -74,6 +74,16 @@ JL_DLLEXPORT jl_jmp_buf *jl_get_safe_restore(void) JL_DLLEXPORT void jl_set_safe_restore(jl_jmp_buf *sr) { +#ifdef _OS_DARWIN_ + jl_task_t *ct = jl_get_current_task(); + if (ct != NULL && ct->ptls) { + if (sr == NULL) + pthread_setspecific(jl_safe_restore_key, (void*)sr); + ct->ptls->safe_restore = sr; + if (sr == NULL) + return; + } +#endif pthread_setspecific(jl_safe_restore_key, (void*)sr); } #endif From 7ce90a399669976899bf4a940864a54bc9ec8ac9 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 16:25:33 +0900 Subject: [PATCH 82/94] fix `exct` for mismatched opaque closure call --- base/compiler/abstractinterpretation.jl | 2 +- test/compiler/inference.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f2d4327668137..5141a0e3c19b6 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2339,7 +2339,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, ocargsig′ = unwrap_unionall(ocargsig) ocargsig′ isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) ocsig = rewrap_unionall(Tuple{Tuple, ocargsig′.parameters...}, ocargsig) - hasintersect(sig, ocsig) || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + hasintersect(sig, ocsig) || return CallMeta(Union{}, Union{MethodError,TypeError}, EFFECTS_THROWS, NoCallInfo()) ocmethod = closure.source::Method result = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv) (; rt, edge, effects, volatile_inf_result) = result diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 7e1fea54830c9..d96e6351aa437 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6089,3 +6089,11 @@ end == Union{} f = issue55627_make_oc() return f(1), f(xs...) end == Tuple{Int,Int} +@test Base.infer_exception_type() do + f = issue55627_make_oc() + return f(1), f() +end >: MethodError +@test Base.infer_exception_type() do + f = issue55627_make_oc() + return f(1), f('1') +end >: TypeError From 8f6a3ef4cb65692b0e6e0d07c9c909d1428187da Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 16:32:25 +0900 Subject: [PATCH 83/94] improve `exct` modeling for opaque closure calls --- base/compiler/abstractinterpretation.jl | 23 +++++++++++++---------- test/compiler/inference.jl | 12 ++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5141a0e3c19b6..5d3208d40ece3 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -42,7 +42,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype), sv::AbsIntState, max_methods::Int) 𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp) - ⊑ₚ, ⊔ₚ, ⊔ᵢ = partialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ) + ⊑ₚ, ⋤ₚ, ⊔ₚ, ⊔ᵢ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ) argtypes = arginfo.argtypes matches = find_method_matches(interp, argtypes, atype; max_methods) if isa(matches, FailedMethodMatch) @@ -97,7 +97,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end - if !(exct ⊑ₚ const_call_result.exct) + if const_call_result.exct ⋤ exct exct = const_call_result.exct (; const_result, edge) = const_call_result else @@ -154,7 +154,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end # Treat the exception type separately. Currently, constprop often cannot determine the exception type # because consistent-cy does not apply to exceptions. - if !(this_exct ⊑ₚ const_call_result.exct) + if const_call_result.exct ⋤ this_exct this_exct = const_call_result.exct (; const_result, edge) = const_call_result else @@ -2342,10 +2342,10 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, hasintersect(sig, ocsig) || return CallMeta(Union{}, Union{MethodError,TypeError}, EFFECTS_THROWS, NoCallInfo()) ocmethod = closure.source::Method result = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig) 𝕃ₚ = ipo_lattice(interp) - ⊑ₚ = ⊑(𝕃ₚ) + ⊑ₚ, ⋤ₚ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ) const_result = volatile_inf_result if !result.edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, @@ -2354,20 +2354,23 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, if const_call_result.rt ⊑ₚ rt (; rt, effects, const_result, edge) = const_call_result end + if const_call_result.exct ⋤ₚ exct + (; exct, const_result, edge) = const_call_result + end end end if check # analyze implicit type asserts on argument and return type - ftt = closure.typ - (aty, rty) = (unwrap_unionall(ftt)::DataType).parameters - rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt) - if !(rt ⊑ₚ rty && tuple_tfunc(𝕃ₚ, arginfo.argtypes[2:end]) ⊑ₚ rewrap_unionall(aty, ftt)) + rty = (unwrap_unionall(tt)::DataType).parameters[2] + rty = rewrap_unionall(rty isa TypeVar ? rty.ub : rty, tt) + if !(rt ⊑ₚ rty && sig ⊑ₚ ocsig) effects = Effects(effects; nothrow=false) + exct = tmerge(𝕃ₚ, exct, TypeError) end end rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) - return CallMeta(rt, Any, effects, info) + return CallMeta(rt, exct, effects, info) end function most_general_argtypes(closure::PartialOpaque) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d96e6351aa437..abd9b7b4e4d1b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6097,3 +6097,15 @@ end >: MethodError f = issue55627_make_oc() return f(1), f('1') end >: TypeError + +# `exct` modeling for opaque closure +oc_exct_1() = Base.Experimental.@opaque function (x) + return x < 0 ? throw(x) : x + end +@test Base.infer_exception_type((Int,)) do x + oc_exct_1()(x) +end == Int +oc_exct_2() = Base.Experimental.@opaque Tuple{Number}->Number (x) -> '1' +@test Base.infer_exception_type((Int,)) do x + oc_exct_2()(x) +end == TypeError From f87d16414dc5cdfd14224322ccd354bb05d41954 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 17:00:21 +0900 Subject: [PATCH 84/94] fix `nothrow` modeling for `invoke` calls --- base/compiler/abstractinterpretation.jl | 3 +++ test/compiler/inference.jl | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5d3208d40ece3..4136ce02f6b2e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2181,6 +2181,9 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) + if !match.fully_covers + effects = Effects(effects; nothrow=false) + end return CallMeta(rt, Any, effects, info) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index abd9b7b4e4d1b..a282a8911ce23 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6109,3 +6109,13 @@ oc_exct_2() = Base.Experimental.@opaque Tuple{Number}->Number (x) -> '1' @test Base.infer_exception_type((Int,)) do x oc_exct_2()(x) end == TypeError + +# nothrow modeling for `invoke` calls +f_invoke_nothrow(::Number) = :number +f_invoke_nothrow(::Int) = :int +@test Base.infer_effects((Int,)) do x + @invoke f_invoke_nothrow(x::Number) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Union{Nothing,Int},)) do x + @invoke f_invoke_nothrow(x::Number) +end |> !Core.Compiler.is_nothrow From 94829611fe2fa6779aff90c54ef06f13def8bc14 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 17:06:21 +0900 Subject: [PATCH 85/94] improve `exct` modeling for `invoke` calls --- base/compiler/abstractinterpretation.jl | 37 ++++++++++++++----------- test/compiler/inference.jl | 30 ++++++++++++++++---- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4136ce02f6b2e..8623a32ddbb2b 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -98,8 +98,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end if const_call_result.exct ⋤ exct - exct = const_call_result.exct - (; const_result, edge) = const_call_result + (; exct, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") end @@ -2135,12 +2134,13 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false) isexact || return CallMeta(Any, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) - if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + types === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + if !(unwrapped isa DataType && unwrapped.name === Tuple.name) + return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()) end argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargtype === Bottom && return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo()) nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below isdispatchelem(ft) || return CallMeta(Any, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below ft = ft::DataType @@ -2154,7 +2154,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector ti = tienv[1]; env = tienv[2]::SimpleVector result = abstract_call_method(interp, method, ti, env, false, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -2168,23 +2168,28 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt # argtypes′[i] = t ⊑ a ? t : a # end 𝕃ₚ = ipo_lattice(interp) + ⊑, ⋤, ⊔ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ) f = singleton_type(ft′) invokecall = InvokeCall(types, lookupsig) const_call_result = abstract_call_method_with_const_args(interp, result, f, arginfo, si, match, sv, invokecall) const_result = volatile_inf_result if const_call_result !== nothing - if ⊑(𝕃ₚ, const_call_result.rt, rt) + if const_call_result.rt ⊑ rt (; rt, effects, const_result, edge) = const_call_result end + if const_call_result.exct ⋤ exct + (; exct, const_result, edge) = const_call_result + end end rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) if !match.fully_covers effects = Effects(effects; nothrow=false) + exct = exct ⊔ TypeError end - return CallMeta(rt, Any, effects, info) + return CallMeta(rt, exct, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -2205,16 +2210,16 @@ end function abstract_throw(interp::AbstractInterpreter, argtypes::Vector{Any}, ::AbsIntState) na = length(argtypes) - 𝕃ᵢ = typeinf_lattice(interp) + ⊔ = join(typeinf_lattice(interp)) if na == 2 argtype2 = argtypes[2] if isvarargtype(argtype2) - exct = tmerge(𝕃ᵢ, unwrapva(argtype2), ArgumentError) + exct = unwrapva(argtype2) ⊔ ArgumentError else exct = argtype2 end elseif na == 3 && isvarargtype(argtypes[3]) - exct = tmerge(𝕃ᵢ, argtypes[2], ArgumentError) + exct = argtypes[2] ⊔ ArgumentError else exct = ArgumentError end @@ -2348,16 +2353,16 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, (; rt, exct, edge, effects, volatile_inf_result) = result match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig) 𝕃ₚ = ipo_lattice(interp) - ⊑ₚ, ⋤ₚ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ) + ⊑, ⋤, ⊔ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ) const_result = volatile_inf_result if !result.edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, nothing, arginfo, si, match, sv) if const_call_result !== nothing - if const_call_result.rt ⊑ₚ rt + if const_call_result.rt ⊑ rt (; rt, effects, const_result, edge) = const_call_result end - if const_call_result.exct ⋤ₚ exct + if const_call_result.exct ⋤ exct (; exct, const_result, edge) = const_call_result end end @@ -2365,9 +2370,9 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, if check # analyze implicit type asserts on argument and return type rty = (unwrap_unionall(tt)::DataType).parameters[2] rty = rewrap_unionall(rty isa TypeVar ? rty.ub : rty, tt) - if !(rt ⊑ₚ rty && sig ⊑ₚ ocsig) + if !(rt ⊑ rty && sig ⊑ ocsig) effects = Effects(effects; nothrow=false) - exct = tmerge(𝕃ₚ, exct, TypeError) + exct = exct ⊔ TypeError end end rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index a282a8911ce23..f15df49d75745 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6078,9 +6078,7 @@ gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, I @test Core.Compiler.return_type(gcondvarargs, Tuple{Vararg{Any}}) === Bool # JuliaLang/julia#55627: argtypes check in `abstract_call_opaque_closure` -issue55627_some_method(x) = 2x -issue55627_make_oc() = Base.Experimental.@opaque (x::Int)->issue55627_some_method(x) - +issue55627_make_oc() = Base.Experimental.@opaque (x::Int) -> 2x @test Base.infer_return_type() do f = issue55627_make_oc() return f(1), f() @@ -6099,9 +6097,7 @@ end >: MethodError end >: TypeError # `exct` modeling for opaque closure -oc_exct_1() = Base.Experimental.@opaque function (x) - return x < 0 ? throw(x) : x - end +oc_exct_1() = Base.Experimental.@opaque (x) -> x < 0 ? throw(x) : x @test Base.infer_exception_type((Int,)) do x oc_exct_1()(x) end == Int @@ -6116,6 +6112,28 @@ f_invoke_nothrow(::Int) = :int @test Base.infer_effects((Int,)) do x @invoke f_invoke_nothrow(x::Number) end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Char,)) do x + @invoke f_invoke_nothrow(x::Number) +end |> !Core.Compiler.is_nothrow @test Base.infer_effects((Union{Nothing,Int},)) do x @invoke f_invoke_nothrow(x::Number) end |> !Core.Compiler.is_nothrow + +# `exct` modeling for `invoke` calls +f_invoke_exct(x::Number) = x < 0 ? throw(x) : x +f_invoke_exct(x::Int) = x +@test Base.infer_exception_type((Int,)) do x + @invoke f_invoke_exct(x::Number) +end == Int +@test Base.infer_exception_type() do + @invoke f_invoke_exct(42::Number) +end == Union{} +@test Base.infer_exception_type((Union{Nothing,Int},)) do x + @invoke f_invoke_exct(x::Number) +end == Union{Int,TypeError} +@test Base.infer_exception_type((Int,)) do x + invoke(f_invoke_exct, Number, x) +end == TypeError +@test Base.infer_exception_type((Char,)) do x + invoke(f_invoke_exct, Tuple{Number}, x) +end == TypeError From 2f0607f419efe0eec405250f3df94878c5928f62 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 4 Sep 2024 07:26:41 -0400 Subject: [PATCH 86/94] show a bit more detail when finished precompiling (#55660) --- base/precompilation.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/precompilation.jl b/base/precompilation.jl index 32fdb86bbdb07..d3f076633f386 100644 --- a/base/precompilation.jl +++ b/base/precompilation.jl @@ -919,8 +919,12 @@ function precompilepkgs(pkgs::Vector{String}=String[]; seconds_elapsed = round(Int, (time_ns() - time_start) / 1e9) ndeps = count(values(was_recompiled)) if ndeps > 0 || !isempty(failed_deps) || (quick_exit && !isempty(std_outputs)) - str = sprint() do iostr + str = sprint(context=io) do iostr if !quick_exit + if fancyprint # replace the progress bar + what = isempty(requested_pkgs) ? "packages finished." : "$(join(requested_pkgs, ", ", " and ")) finished." + printpkgstyle(iostr, :Precompiling, what) + end plural = length(configs) > 1 ? "dependency configurations" : ndeps == 1 ? "dependency" : "dependencies" print(iostr, " $(ndeps) $(plural) successfully precompiled in $(seconds_elapsed) seconds") if n_already_precomp > 0 || !isempty(circular_deps) From 53d3ca9855db0308ddf2044a3a0f21f3de492cf3 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Wed, 4 Sep 2024 21:08:14 +0800 Subject: [PATCH 87/94] subtype: minor clean up for fast path for lhs union and rhs typevar (#55645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up #55413. The error pattern mentioned in https://github.com/JuliaLang/julia/pull/55413#issuecomment-2288384468 care's `∃y`'s ub in env rather than its original ub. So it seems more robust to check the bounds in env directly. The equivalent typevar propagation is lifted from `subtype_var` for the same reason. --- src/subtype.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 4118bbeab649b..2011ca8b1c705 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -1304,6 +1304,7 @@ static int subtype_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, in } static int try_subtype_by_bounds(jl_value_t *a, jl_value_t *b, jl_stenv_t *e); +static int has_exists_typevar(jl_value_t *x, jl_stenv_t *e) JL_NOTSAFEPOINT; // `param` means we are currently looking at a parameter of a type constructor // (as opposed to being outside any type constructor, or comparing variable bounds). @@ -1314,7 +1315,7 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) if (jl_is_uniontype(x)) { if (obviously_egal(x, y)) return 1; - if (e->Runions.depth == 0 && jl_is_typevar(y) && !jl_has_free_typevars(x) && !jl_has_free_typevars(((jl_tvar_t*)y)->ub)) { + if (e->Runions.depth == 0 && jl_is_typevar(y) && !jl_has_free_typevars(x)) { // Similar to fast path for repeated elements: if there have been no outer // unions on the right, and the right side is a typevar, then we can handle the // typevar first before picking a union element, under the theory that it may @@ -1325,7 +1326,17 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // free typevars, since the typevars presence might lead to those elements // getting eliminated (omit_bad_union) or degenerate (Union{Ptr{T}, Ptr}) or // combined (Union{T, S} where {T, S <: T}). - return subtype_var((jl_tvar_t*)y, x, e, 1, param); + jl_tvar_t *yvar = (jl_tvar_t *)y; + jl_varbinding_t *yb = lookup(e, yvar); + while (e->intersection && yb != NULL && yb->lb == yb->ub && jl_is_typevar(yb->lb)) { + yvar = (jl_tvar_t *)yb->lb; + yb = lookup(e, yvar); + } + // Note: `x <: ∃y` performs a local ∀-∃ check between `x` and `yb->ub`. + // We need to ensure that there's no ∃ typevar as otherwise that check + // might cause false alarm due to the accumulated env change. + if (yb == NULL || yb->right == 0 || !has_exists_typevar(yb->ub, e)) + return subtype_var(yvar, x, e, 1, param); } x = pick_union_element(x, e, 0); } From e8188631cd019d86b4899b202b4312041febff73 Mon Sep 17 00:00:00 2001 From: Eduardo Souza Date: Thu, 5 Sep 2024 00:05:49 +1000 Subject: [PATCH 88/94] Adding `JL_DATA_TYPE` annotation to `_jl_globalref_t` (#55684) `_jl_globalref_t` seems to be allocated in the heap, and there is an object `jl_globalref_type` which indicates that it is in fact, a data type, thus it should be annotated with `JL_DATA_TYPE`?? --- src/julia.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/julia.h b/src/julia.h index 589a5745ff59f..efdd6a1b08bf7 100644 --- a/src/julia.h +++ b/src/julia.h @@ -718,6 +718,7 @@ typedef struct _jl_module_t { } jl_module_t; struct _jl_globalref_t { + JL_DATA_TYPE jl_module_t *mod; jl_sym_t *name; jl_binding_t *binding; From 351727f44651d995ef061c76a7ee058b37661111 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 4 Sep 2024 11:13:05 -0300 Subject: [PATCH 89/94] Make GEP when loading the PTLS an inbounds one. (#55682) Non inbounds GEPs should only be used when doing pointer arithmethic i.e Ptr or MemoryRef boundscheck. Found when auditing non inbounds GEPs for https://github.com/JuliaLang/julia/pull/55681 --- src/llvm-ptls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index 9e49aa5ba2f39..736c1acd9525a 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -128,7 +128,7 @@ Instruction *LowerPTLS::emit_pgcstack_tp(Value *offset, Instruction *insertBefor offset = ConstantInt::getSigned(T_size, jl_tls_offset); auto tp = InlineAsm::get(FunctionType::get(PointerType::get(builder.getContext(), 0), false), asm_str, "=r", false); tls = builder.CreateCall(tp, {}, "thread_ptr"); - tls = builder.CreateGEP(Type::getInt8Ty(builder.getContext()), tls, {offset}, "tls_ppgcstack"); + tls = builder.CreateInBoundsGEP(Type::getInt8Ty(builder.getContext()), tls, {offset}, "tls_ppgcstack"); } return builder.CreateLoad(T_pppjlvalue, tls, "tls_pgcstack"); } From 68d04bad49515ea6f246f826be0fec7445e3e7ba Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 4 Sep 2024 11:16:00 -0300 Subject: [PATCH 90/94] codegen: make boundscheck GEP not be inbounds while the load GEP is inbounds (#55681) Avoids undefined behavior on the boundschecking arithmetic, which is correct only assuming overflow follows unsigned arithmetic wrap around rules. Also add names to the Memory related LLVM instructions to aid debugging Closes: https://github.com/JuliaLang/julia/pull/55674 --- src/cgutils.cpp | 36 ++++++++++++++++++++++++++++++------ src/codegen.cpp | 8 ++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 2d2d2aed22069..bec84d9901279 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -3026,12 +3026,15 @@ static Value *emit_genericmemoryelsize(jl_codectx_t &ctx, Value *v, jl_value_t * size_t sz = sty->layout->size; if (sty->layout->flags.arrayelem_isunion) sz++; - return ConstantInt::get(ctx.types().T_size, sz); + auto elsize = ConstantInt::get(ctx.types().T_size, sz); + return elsize; } else { Value *t = emit_typeof(ctx, v, false, false, true); Value *elsize = emit_datatype_size(ctx, t, add_isunion); - return ctx.builder.CreateZExt(elsize, ctx.types().T_size); + elsize = ctx.builder.CreateZExt(elsize, ctx.types().T_size); + setName(ctx.emission_context, elsize, "elsize"); + return elsize; } } @@ -3066,6 +3069,7 @@ static Value *emit_genericmemorylen(jl_codectx_t &ctx, Value *addr, jl_value_t * MDBuilder MDB(ctx.builder.getContext()); auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, genericmemoryype_maxsize(typ))); LI->setMetadata(LLVMContext::MD_range, rng); + setName(ctx.emission_context, LI, "memory_len"); return LI; } @@ -3075,7 +3079,7 @@ static Value *emit_genericmemoryptr(jl_codectx_t &ctx, Value *mem, const jl_data Value *addr = mem; addr = decay_derived(ctx, addr); addr = ctx.builder.CreateStructGEP(ctx.types().T_jlgenericmemory, addr, 1); - setName(ctx.emission_context, addr, ".data_ptr"); + setName(ctx.emission_context, addr, "memory_data_ptr"); PointerType *PPT = cast(ctx.types().T_jlgenericmemory->getElementType(1)); LoadInst *LI = ctx.builder.CreateAlignedLoad(PPT, addr, Align(sizeof(char*))); LI->setOrdering(AtomicOrdering::NotAtomic); @@ -3087,6 +3091,7 @@ static Value *emit_genericmemoryptr(jl_codectx_t &ctx, Value *mem, const jl_data assert(AS == AddressSpace::Loaded); ptr = ctx.builder.CreateCall(prepare_call(gc_loaded_func), { mem, ptr }); } + setName(ctx.emission_context, ptr, "memory_data"); return ptr; } @@ -4195,6 +4200,7 @@ static jl_cgval_t _emit_memoryref(jl_codectx_t &ctx, Value *mem, Value *data, co Value *ref = Constant::getNullValue(get_memoryref_type(ctx.builder.getContext(), ctx.types().T_size, layout, 0)); ref = ctx.builder.CreateInsertValue(ref, data, 0); ref = ctx.builder.CreateInsertValue(ref, mem, 1); + setName(ctx.emission_context, ref, "memory_ref"); return mark_julia_type(ctx, ref, false, typ); } @@ -4215,6 +4221,7 @@ static Value *emit_memoryref_FCA(jl_codectx_t &ctx, const jl_cgval_t &ref, const LoadInst *load = ctx.builder.CreateLoad(type, data_pointer(ctx, ref)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ref.tbaa); ai.decorateInst(load); + setName(ctx.emission_context, load, "memory_ref_FCA"); return load; } else { @@ -4231,9 +4238,12 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg return jl_cgval_t(); Value *V = emit_memoryref_FCA(ctx, ref, layout); Value *data = CreateSimplifiedExtractValue(ctx, V, 0); + maybeSetName(ctx.emission_context, data, "memoryref_data"); Value *mem = CreateSimplifiedExtractValue(ctx, V, 1); + maybeSetName(ctx.emission_context, mem, "memoryref_mem"); Value *i = emit_unbox(ctx, ctx.types().T_size, idx, (jl_value_t*)jl_long_type); Value *offset = ctx.builder.CreateSub(i, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, offset, "memoryref_offset"); Value *elsz = emit_genericmemoryelsize(ctx, mem, ref.typ, false); bool bc = bounds_check_enabled(ctx, inbounds); #if 1 @@ -4245,12 +4255,14 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg bool isghost = layout->size == 0; if ((!isboxed && isunion) || isghost) { newdata = ctx.builder.CreateAdd(data, offset); + setName(ctx.emission_context, newdata, "memoryref_data+offset"); if (bc) { BasicBlock *failBB, *endBB; failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); Value *inbound = ctx.builder.CreateICmpULT(newdata, mlen); + setName(ctx.emission_context, offset, "memoryref_isinbounds"); ctx.builder.CreateCondBr(inbound, endBB, failBB); failBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(failBB); @@ -4278,10 +4290,13 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg // and we can further rearrange that as ovflw = !( offset+len < len+len ) as unsigned math Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); ovflw = ctx.builder.CreateICmpUGE(ctx.builder.CreateAdd(offset, mlen), ctx.builder.CreateNUWAdd(mlen, mlen)); + setName(ctx.emission_context, ovflw, "memoryref_ovflw"); } #endif boffset = ctx.builder.CreateMul(offset, elsz); - newdata = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), data, boffset); + setName(ctx.emission_context, boffset, "memoryref_byteoffset"); + newdata = ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), data, boffset); + setName(ctx.emission_context, newdata, "memoryref_data_byteoffset"); (void)boffset; // LLVM is very bad at handling GEP with types different from the load if (bc) { BasicBlock *failBB, *endBB; @@ -4304,8 +4319,11 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg ctx.builder.CreatePtrToInt(newdata, ctx.types().T_size), ctx.builder.CreatePtrToInt(mptr, ctx.types().T_size)); Value *blen = ctx.builder.CreateMul(mlen, elsz, "", true, true); + setName(ctx.emission_context, blen, "memoryref_bytelen"); Value *inbound = ctx.builder.CreateICmpULT(bidx0, blen); + setName(ctx.emission_context, inbound, "memoryref_isinbounds"); inbound = ctx.builder.CreateAnd(ctx.builder.CreateNot(ovflw), inbound); + setName(ctx.emission_context, inbound, "memoryref_isinbounds¬ovflw"); #else Value *idx0; // (newdata - mptr) / elsz idx0 = ctx.builder.CreateSub( @@ -4342,8 +4360,10 @@ static jl_cgval_t emit_memoryref_offset(jl_codectx_t &ctx, const jl_cgval_t &ref offset = ctx.builder.CreateSub( ctx.builder.CreatePtrToInt(data, ctx.types().T_size), ctx.builder.CreatePtrToInt(mptr, ctx.types().T_size)); + setName(ctx.emission_context, offset, "memoryref_offset"); Value *elsz = emit_genericmemoryelsize(ctx, mem, ref.typ, false); offset = ctx.builder.CreateExactUDiv(offset, elsz); + setName(ctx.emission_context, offset, "memoryref_offsetidx"); } offset = ctx.builder.CreateAdd(offset, ConstantInt::get(ctx.types().T_size, 1)); return mark_julia_type(ctx, offset, false, jl_long_type); @@ -4352,7 +4372,9 @@ static jl_cgval_t emit_memoryref_offset(jl_codectx_t &ctx, const jl_cgval_t &ref static Value *emit_memoryref_mem(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) { Value *V = emit_memoryref_FCA(ctx, ref, layout); - return CreateSimplifiedExtractValue(ctx, V, 1); + V = CreateSimplifiedExtractValue(ctx, V, 1); + maybeSetName(ctx.emission_context, V, "memoryref_mem"); + return V; } static Value *emit_memoryref_ptr(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) @@ -4374,13 +4396,15 @@ static Value *emit_memoryref_ptr(jl_codectx_t &ctx, const jl_cgval_t &ref, const data = ctx.builder.CreateCall(prepare_call(gc_loaded_func), { mem, data }); if (!GEPlist.empty()) { for (auto &GEP : make_range(GEPlist.rbegin(), GEPlist.rend())) { - Instruction *GEP2 = GEP->clone(); + GetElementPtrInst *GEP2 = cast(GEP->clone()); GEP2->mutateType(PointerType::get(GEP->getResultElementType(), AS)); GEP2->setOperand(GetElementPtrInst::getPointerOperandIndex(), data); + GEP2->setIsInBounds(true); ctx.builder.Insert(GEP2); data = GEP2; } } + setName(ctx.emission_context, data, "memoryref_data"); return data; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 4091ec6c03db0..9f80791f2882d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -171,6 +171,14 @@ void setName(jl_codegen_params_t ¶ms, Value *V, const Twine &Name) } } +void maybeSetName(jl_codegen_params_t ¶ms, Value *V, const Twine &Name) +{ + // To be used when we may get an Instruction or something that is not an instruction i.e Constants/Arguments + if (params.debug_level >= 2 && isa(V)) { + V->setName(Name); + } +} + void setName(jl_codegen_params_t ¶ms, Value *V, std::function GetName) { assert((isa(V) || isa(V)) && "Should only set names on instructions!"); From e217f938d291854caaca4e5b81bbeb27c3e985ba Mon Sep 17 00:00:00 2001 From: Nathan Zimmerberg <39104088+nhz2@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:18:02 -0400 Subject: [PATCH 91/94] Make `rename` public (#55652) Fixes #41584. Follow up of #55503 I think `rename` is a very useful low-level file system operation. Many other programming languages have this function, so it is useful when porting IO code to Julia. One use case is to improve the Zarr.jl package to be more compatible with zarr-python. https://github.com/zarr-developers/zarr-python/blob/0b5483a7958e2ae5512a14eb424a84b2a75dd727/src/zarr/v2/storage.py#L994 uses the `os.replace` function. It would be nice to be able to directly use `Base.rename` as a replacement for `os.replace` to ensure compatibility. Another use case is writing a safe zip file extractor in pure Julia. https://github.com/madler/sunzip/blob/34107fa9e2a2e36e7e72725dc4c58c9ad6179898/sunzip.c#L365 uses the `rename` function to do this in C. Lastly in https://github.com/medyan-dev/MEDYANSimRunner.jl/blob/67d5b42cc599670486d5d640260a95e951091f7a/src/file-saving.jl#L83 I am using `ccall(:jl_fs_rename` to save files, because I have large numbers of Julia processes creating and reading these files at the same time on a distributed file system on a cluster, so I don't want data to become corrupted if one of the nodes crashes (which happens fairly regularly). However `jl_fs_rename` is not public, and might break in a future release. This PR also adds a note to `mv` comparing it to the `mv` command, similar to the note on the `cp` function. --- base/file.jl | 17 ++++++++++++++++- base/public.jl | 3 +++ doc/src/base/file.md | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/base/file.jl b/base/file.jl index d45f34fb55dc6..567783c4b1e5b 100644 --- a/base/file.jl +++ b/base/file.jl @@ -385,7 +385,7 @@ of the file or directory `src` refers to. Return `dst`. !!! note - The `cp` function is different from the `cp` command. The `cp` function always operates on + The `cp` function is different from the `cp` Unix command. The `cp` function always operates on the assumption that `dst` is a file, while the command does different things depending on whether `dst` is a directory or a file. Using `force=true` when `dst` is a directory will result in loss of all the contents present @@ -438,6 +438,16 @@ julia> mv("hello.txt", "goodbye.txt", force=true) julia> rm("goodbye.txt"); ``` + +!!! note + The `mv` function is different from the `mv` Unix command. The `mv` function by + default will error if `dst` exists, while the command will delete + an existing `dst` file by default. + Also the `mv` function always operates on + the assumption that `dst` is a file, while the command does different things depending + on whether `dst` is a directory or a file. + Using `force=true` when `dst` is a directory will result in loss of all the contents present + in the `dst` directory, and `dst` will become a file that has the contents of `src` instead. """ function mv(src::AbstractString, dst::AbstractString; force::Bool=false) if force @@ -1192,6 +1202,8 @@ If a path contains a "\\0" throw an `ArgumentError`. On other failures throw an `IOError`. Return `newpath`. +This is a lower level filesystem operation used to implement [`mv`](@ref). + OS-specific restrictions may apply when `oldpath` and `newpath` are in different directories. Currently there are a few differences in behavior on Windows which may be resolved in a future release. @@ -1202,6 +1214,9 @@ Specifically, currently on Windows: 4. `rename` may remove `oldpath` if it is a hardlink to `newpath`. See also: [`mv`](@ref). + +!!! compat "Julia 1.12" + This method was made public in Julia 1.12. """ function rename(oldpath::AbstractString, newpath::AbstractString) err = ccall(:jl_fs_rename, Int32, (Cstring, Cstring), oldpath, newpath) diff --git a/base/public.jl b/base/public.jl index 862aff48da63e..803766a0cec1b 100644 --- a/base/public.jl +++ b/base/public.jl @@ -110,6 +110,9 @@ public reseteof, link_pipe!, +# filesystem operations + rename, + # misc notnothing, runtests, diff --git a/doc/src/base/file.md b/doc/src/base/file.md index 22799f882bb26..300738a39322d 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -29,6 +29,7 @@ Base.Filesystem.operm Base.Filesystem.cp Base.download Base.Filesystem.mv +Base.Filesystem.rename Base.Filesystem.rm Base.Filesystem.touch Base.Filesystem.tempname From 4aac5ae31e12721c60916ac79e0ca9f974fdbec0 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:29:16 -0400 Subject: [PATCH 92/94] contrib: include private libdir in `ldflags` on macOS (#55687) The private libdir is used on macOS, so it needs to be included in our `ldflags` --- contrib/julia-config.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/julia-config.jl b/contrib/julia-config.jl index df17b967c1ed7..c692b3f522fb2 100755 --- a/contrib/julia-config.jl +++ b/contrib/julia-config.jl @@ -67,9 +67,7 @@ function ldlibs(doframework) "julia" end if Sys.isunix() - return "-Wl,-rpath,$(shell_escape(libDir())) " * - (Sys.isapple() ? string() : "-Wl,-rpath,$(shell_escape(private_libDir())) ") * - "-l$libname" + return "-Wl,-rpath,$(shell_escape(libDir())) -Wl,-rpath,$(shell_escape(private_libDir())) -l$libname" else return "-l$libname -lopenlibm" end From bada969c14fd8f58804b662f5cca44808bb52433 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 4 Sep 2024 18:35:33 -0400 Subject: [PATCH 93/94] Profile.print: Shorten C paths too (#55683) --- base/sysinfo.jl | 2 ++ stdlib/Profile/src/Profile.jl | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/base/sysinfo.jl b/base/sysinfo.jl index d0dcac8c6d416..7dab313cf4f57 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -56,6 +56,8 @@ global STDLIB::String = "$BINDIR/../share/julia/stdlib/v$(VERSION.major).$(VERSI # In case STDLIB change after julia is built, the variable below can be used # to update cached method locations to updated ones. const BUILD_STDLIB_PATH = STDLIB +# Similarly, this is the root of the julia repo directory that julia was built from +const BUILD_ROOT_PATH = "$BINDIR/../.." # helper to avoid triggering precompile warnings diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index a80e6c71e5aef..c7ef1efb35945 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -503,13 +503,23 @@ function flatten(data::Vector, lidict::LineInfoDict) return (newdata, newdict) end +const SRC_DIR = normpath(joinpath(Sys.BUILD_ROOT_PATH, "src")) + # Take a file-system path and try to form a concise representation of it # based on the package ecosystem function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,String,String}}) return get!(filenamecache, spath) do path = Base.fixup_stdlib_path(string(spath)) + path_norm = normpath(path) possible_base_path = normpath(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) - if isabspath(path) + lib_dir = abspath(Sys.BINDIR, Base.LIBDIR) + if startswith(path_norm, SRC_DIR) + remainder = only(split(path_norm, SRC_DIR, keepempty=false)) + return (isfile(path_norm) ? path_norm : ""), "@juliasrc", remainder + elseif startswith(path_norm, lib_dir) + remainder = only(split(path_norm, lib_dir, keepempty=false)) + return (isfile(path_norm) ? path_norm : ""), "@julialib", remainder + elseif isabspath(path) if ispath(path) # try to replace the file-system prefix with a short "@Module" one, # assuming that profile came from the current machine From 6f04ee0ff34f727dfd44199d6e1132d2235ebedd Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Thu, 5 Sep 2024 21:54:11 -0300 Subject: [PATCH 94/94] [LLVMLibUnwindJLL] Update llvmlibunwind to 14.0.6 (#48140) --- deps/checksums/llvmunwind | 32 ++++ deps/llvmunwind.version | 2 +- ...ibunwind-revert-monorepo-requirement.patch | 156 ------------------ deps/unwind.mk | 49 +++--- stdlib/LLVMLibUnwind_jll/Project.toml | 2 +- 5 files changed, 59 insertions(+), 182 deletions(-) delete mode 100644 deps/patches/llvm-libunwind-revert-monorepo-requirement.patch diff --git a/deps/checksums/llvmunwind b/deps/checksums/llvmunwind index e69de29bb2d1d..a90d28717dd85 100644 --- a/deps/checksums/llvmunwind +++ b/deps/checksums/llvmunwind @@ -0,0 +1,32 @@ +LLVMLibUnwind.v14.0.6+0.aarch64-apple-darwin.tar.gz/md5/d8584e0e3dc26ea7404d3719cea9e233 +LLVMLibUnwind.v14.0.6+0.aarch64-apple-darwin.tar.gz/sha512/7a0396eaace91b9b4d013c209605d468a7ff9b99ede9fdd57602539a6fa6f3ea84a440f32840056a1234df3ef1896739ea0820fee72b4f208096c553fc54adb9 +LLVMLibUnwind.v14.0.6+0.aarch64-linux-gnu.tar.gz/md5/d6edea561b61173d05aa79936e49f6b7 +LLVMLibUnwind.v14.0.6+0.aarch64-linux-gnu.tar.gz/sha512/9fbe29ec6a33c719bc9a4dd19911ceded9622269c042192d339a6cf45aa8209ad64c424167c094ca01293438af5930f091acba0538b3fe640a746297f5cc8cb3 +LLVMLibUnwind.v14.0.6+0.aarch64-linux-musl.tar.gz/md5/3ec68c87e4bddd024ee0ca6adc2b3b96 +LLVMLibUnwind.v14.0.6+0.aarch64-linux-musl.tar.gz/sha512/be3cd9d5510c2693dee1494c36c479d32311ff83f5b2d31c08508a3dd370788961ce46e9025afe148a0febd05942fd294370a357dd717bee353d8a108617f6de +LLVMLibUnwind.v14.0.6+0.armv6l-linux-gnueabihf.tar.gz/md5/8ca5a926d69124225d485d679232a54f +LLVMLibUnwind.v14.0.6+0.armv6l-linux-gnueabihf.tar.gz/sha512/353f540b342bc54877e7a41fe65c9eeac525fd91bf4cddbe1b3ec2ed93c3751beaf8316a4d31530502b067100b160301262e10cbe4407db3abf1ceb5d9a74eb2 +LLVMLibUnwind.v14.0.6+0.armv6l-linux-musleabihf.tar.gz/md5/4e5b576958f2a2e708eb5918ceef0de0 +LLVMLibUnwind.v14.0.6+0.armv6l-linux-musleabihf.tar.gz/sha512/2e98c472d3ee25c2e062efa4eb21ac9cfc49b26ea9d99ad4a8e7660c4c09f121d31193bd161f54ea332ce94785d601897311e9e6668adb1e25e2b666e0d5bb3f +LLVMLibUnwind.v14.0.6+0.armv7l-linux-gnueabihf.tar.gz/md5/1c81a886e799663ce8d04400c5b516a9 +LLVMLibUnwind.v14.0.6+0.armv7l-linux-gnueabihf.tar.gz/sha512/236b78b9a17eaae74ab07349ac8dde16c3abbd48e0d075abd1c195d60efff48e2fbf799554df114ea3d3dba937e0369430a2788bde2a1201126e026ef6cdac42 +LLVMLibUnwind.v14.0.6+0.armv7l-linux-musleabihf.tar.gz/md5/0371f43ebcb571d0a635739252b88986 +LLVMLibUnwind.v14.0.6+0.armv7l-linux-musleabihf.tar.gz/sha512/605318ae3737e26ff89d6291311a7db3bc3ec7c8d1f2e72ae40fd3d9df0754ee2ebfb77687122605f26d76d62effb85157bc39982814920d5af46c124e71a5ff +LLVMLibUnwind.v14.0.6+0.i686-linux-gnu.tar.gz/md5/cd3f1cdf404b6102754ced4bd3a890f6 +LLVMLibUnwind.v14.0.6+0.i686-linux-gnu.tar.gz/sha512/65fe2c5b1e04da1e1d8111a0b0083fa0fa9447eaea7af7a018c09fe6d5506566c491bbad296a7be8c488ca3495016ae16a6879d69f057f8866d94910147dee03 +LLVMLibUnwind.v14.0.6+0.i686-linux-musl.tar.gz/md5/abac9b416d2ba5abcf5ce849f43ffa96 +LLVMLibUnwind.v14.0.6+0.i686-linux-musl.tar.gz/sha512/fed677ed6f103c56eb9dd4578fa37a56ed2a4bc803aa1997c5af19762a623d2f82db1f72f429448d66fcef3b37af2104e6cb782f023aaabef086a921a862b042 +LLVMLibUnwind.v14.0.6+0.i686-w64-mingw32.tar.gz/md5/4c71ffd7c8cabb1c0ed6290b193883c5 +LLVMLibUnwind.v14.0.6+0.i686-w64-mingw32.tar.gz/sha512/6b1421a3268170467225112167cdb33fec962181993a2dad5594d4ee0623ac88ee0588cdc7d0656dc1cb9129ef96f621a97a224731cd161134d7d63c8fd32c16 +LLVMLibUnwind.v14.0.6+0.powerpc64le-linux-gnu.tar.gz/md5/06faf505f0dc354afcd01113cfc57af2 +LLVMLibUnwind.v14.0.6+0.powerpc64le-linux-gnu.tar.gz/sha512/1f9dfbd403e2ce121e126c217baede178cb1323012bb5e3cd1f778ff51e4216aed9dd69036e2baffbd60a6f5ae438ddaba6c13809459e94bb00be3f7bfc8c30e +LLVMLibUnwind.v14.0.6+0.x86_64-apple-darwin.tar.gz/md5/516a11d99306e3f214968a7951b07a06 +LLVMLibUnwind.v14.0.6+0.x86_64-apple-darwin.tar.gz/sha512/885738599bbd96f20083f9b9368ce3f243bd5868d3ac9a45189de6cb40b6664a6dcdaece159989e504670231db8c2addfa8d544003eb0cdabba960e4ab6a4470 +LLVMLibUnwind.v14.0.6+0.x86_64-linux-gnu.tar.gz/md5/d851b90ea3f9664774316169fc494e21 +LLVMLibUnwind.v14.0.6+0.x86_64-linux-gnu.tar.gz/sha512/a1f529454f0881baaa508481ba97ecffb040fa92141b4cbc72278adcf8b84f0766fa918aea7fb99ce690c4fd80c36fec365987625db42f4e7bb36ad24ce177d0 +LLVMLibUnwind.v14.0.6+0.x86_64-linux-musl.tar.gz/md5/dc4e86eb2effe1f6cb0d0ceda635f226 +LLVMLibUnwind.v14.0.6+0.x86_64-linux-musl.tar.gz/sha512/c52de384853890f9df81aa9e422c1ba3fde12b2ae9c7b60b9ecdc6d0c88eab495dd336af2b6cd2c31d6eddcd0a213954eadbc7884bc39ce2039cec672eac32fe +LLVMLibUnwind.v14.0.6+0.x86_64-unknown-freebsd.tar.gz/md5/8477e3624c73a820d8ab82a53e1e10fa +LLVMLibUnwind.v14.0.6+0.x86_64-unknown-freebsd.tar.gz/sha512/32ce031245a5b59a779cd77fa3c9bf05ee59e48c913b75d4964bea49f37da232c59a42ad993f7b5edc88322148c1d7394984349682bfce3b69d33a51756ac8e3 +LLVMLibUnwind.v14.0.6+0.x86_64-w64-mingw32.tar.gz/md5/7be93eccbdb0aff427c43af651073d66 +LLVMLibUnwind.v14.0.6+0.x86_64-w64-mingw32.tar.gz/sha512/89a61a81ec664c72107ac09e717200b00434350bf77064267180bc0c101a59e0ee8c8af4dd6fe75eacdeb14e82743c138b2fc558ca08550d8796b8db93f89da4 diff --git a/deps/llvmunwind.version b/deps/llvmunwind.version index 7d13af9a158f7..9c2a91c566ba2 100644 --- a/deps/llvmunwind.version +++ b/deps/llvmunwind.version @@ -2,4 +2,4 @@ LLVMUNWIND_JLL_NAME := LLVMLibUnwind ## source build -LLVMUNWIND_VER := 12.0.1 +LLVMUNWIND_VER := 14.0.6 diff --git a/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch b/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch deleted file mode 100644 index 4e3897dfb9801..0000000000000 --- a/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch +++ /dev/null @@ -1,156 +0,0 @@ -Upstream commit 8c03fdf34a659925a3f09c8f54016e47ea1c7519 changed the build such -that it requires living inside the monorepo with libcxx available, only so that -it can reuse a CMake file to simplify some build steps. This patch is a revert -of that commit applied only to libunwind. - ---- -diff --git a/libunwind/CMakeLists.txt b/libunwind/CMakeLists.txt -index 570b8db90653..a383d7d77d6f 100644 ---- a/libunwind/CMakeLists.txt -+++ b/libunwind/CMakeLists.txt -@@ -1,7 +1,3 @@ --if (NOT IS_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../libcxx") -- message(FATAL_ERROR "libunwind requires being built in a monorepo layout with libcxx available") --endif() -- - #=============================================================================== - # Setup Project - #=============================================================================== -@@ -15,31 +11,103 @@ set(CMAKE_MODULE_PATH - ${CMAKE_MODULE_PATH} - ) - --set(LIBUNWIND_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) --set(LIBUNWIND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) --set(LIBUNWIND_LIBCXX_PATH "${CMAKE_CURRENT_LIST_DIR}/../libcxx" CACHE PATH -- "Specify path to libc++ source.") -- - if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR OR LIBUNWIND_STANDALONE_BUILD) - project(libunwind LANGUAGES C CXX ASM) - -+ # Rely on llvm-config. -+ set(CONFIG_OUTPUT) -+ if(NOT LLVM_CONFIG_PATH) -+ find_program(LLVM_CONFIG_PATH "llvm-config") -+ endif() -+ if (DEFINED LLVM_PATH) -+ set(LLVM_INCLUDE_DIR ${LLVM_INCLUDE_DIR} CACHE PATH "Path to llvm/include") -+ set(LLVM_PATH ${LLVM_PATH} CACHE PATH "Path to LLVM source tree") -+ set(LLVM_MAIN_SRC_DIR ${LLVM_PATH}) -+ set(LLVM_CMAKE_PATH "${LLVM_PATH}/cmake/modules") -+ elseif(LLVM_CONFIG_PATH) -+ message(STATUS "Found LLVM_CONFIG_PATH as ${LLVM_CONFIG_PATH}") -+ set(CONFIG_COMMAND ${LLVM_CONFIG_PATH} "--includedir" "--prefix" "--src-root") -+ execute_process(COMMAND ${CONFIG_COMMAND} -+ RESULT_VARIABLE HAD_ERROR -+ OUTPUT_VARIABLE CONFIG_OUTPUT) -+ if (NOT HAD_ERROR) -+ string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" -+ CONFIG_OUTPUT ${CONFIG_OUTPUT}) -+ else() -+ string(REPLACE ";" " " CONFIG_COMMAND_STR "${CONFIG_COMMAND}") -+ message(STATUS "${CONFIG_COMMAND_STR}") -+ message(FATAL_ERROR "llvm-config failed with status ${HAD_ERROR}") -+ endif() -+ -+ list(GET CONFIG_OUTPUT 0 INCLUDE_DIR) -+ list(GET CONFIG_OUTPUT 1 LLVM_OBJ_ROOT) -+ list(GET CONFIG_OUTPUT 2 MAIN_SRC_DIR) -+ -+ set(LLVM_INCLUDE_DIR ${INCLUDE_DIR} CACHE PATH "Path to llvm/include") -+ set(LLVM_BINARY_DIR ${LLVM_OBJ_ROOT} CACHE PATH "Path to LLVM build tree") -+ set(LLVM_MAIN_SRC_DIR ${MAIN_SRC_DIR} CACHE PATH "Path to LLVM source tree") -+ set(LLVM_LIT_PATH "${LLVM_PATH}/utils/lit/lit.py") -+ -+ # --cmakedir is supported since llvm r291218 (4.0 release) -+ execute_process( -+ COMMAND ${LLVM_CONFIG_PATH} --cmakedir -+ RESULT_VARIABLE HAD_ERROR -+ OUTPUT_VARIABLE CONFIG_OUTPUT -+ ERROR_QUIET) -+ if(NOT HAD_ERROR) -+ string(STRIP "${CONFIG_OUTPUT}" LLVM_CMAKE_PATH_FROM_LLVM_CONFIG) -+ file(TO_CMAKE_PATH "${LLVM_CMAKE_PATH_FROM_LLVM_CONFIG}" LLVM_CMAKE_PATH) -+ else() -+ file(TO_CMAKE_PATH "${LLVM_BINARY_DIR}" LLVM_BINARY_DIR_CMAKE_STYLE) -+ set(LLVM_CMAKE_PATH "${LLVM_BINARY_DIR_CMAKE_STYLE}/lib${LLVM_LIBDIR_SUFFIX}/cmake/llvm") -+ endif() -+ else() -+ message(WARNING "UNSUPPORTED LIBUNWIND CONFIGURATION DETECTED: " -+ "llvm-config not found and LLVM_MAIN_SRC_DIR not defined. " -+ "Reconfigure with -DLLVM_CONFIG=path/to/llvm-config " -+ "or -DLLVM_PATH=path/to/llvm-source-root.") -+ endif() -+ -+ if (EXISTS ${LLVM_CMAKE_PATH}) -+ list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_PATH}") -+ include("${LLVM_CMAKE_PATH}/AddLLVM.cmake") -+ include("${LLVM_CMAKE_PATH}/HandleLLVMOptions.cmake") -+ else() -+ message(WARNING "Not found: ${LLVM_CMAKE_PATH}") -+ endif() -+ - set(PACKAGE_NAME libunwind) - set(PACKAGE_VERSION 12.0.1) - set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") - set(PACKAGE_BUGREPORT "llvm-bugs@lists.llvm.org") - -- # Add the CMake module path of libcxx so we can reuse HandleOutOfTreeLLVM.cmake -- set(LIBUNWIND_LIBCXX_CMAKE_PATH "${LIBUNWIND_LIBCXX_PATH}/cmake/Modules") -- list(APPEND CMAKE_MODULE_PATH "${LIBUNWIND_LIBCXX_CMAKE_PATH}") -+ if (EXISTS ${LLVM_MAIN_SRC_DIR}/utils/lit/lit.py) -+ set(LLVM_LIT ${LLVM_MAIN_SRC_DIR}/utils/lit/lit.py) -+ else() -+ # Seek installed Lit. -+ find_program(LLVM_LIT "lit.py" ${LLVM_MAIN_SRC_DIR}/utils/lit -+ DOC "Path to lit.py") -+ endif() - -- # In a standalone build, we don't have llvm to automatically generate the -- # llvm-lit script for us. So we need to provide an explicit directory that -- # the configurator should write the script into. -- set(LIBUNWIND_STANDALONE_BUILD 1) -- set(LLVM_LIT_OUTPUT_DIR "${LIBUNWIND_BINARY_DIR}/bin") -+ if (LLVM_LIT) -+ # Define the default arguments to use with 'lit', and an option for the user -+ # to override. -+ set(LIT_ARGS_DEFAULT "-sv") -+ if (MSVC OR XCODE) -+ set(LIT_ARGS_DEFAULT "${LIT_ARGS_DEFAULT} --no-progress-bar") -+ endif() -+ set(LLVM_LIT_ARGS "${LIT_ARGS_DEFAULT}" CACHE STRING "Default options for lit") -+ -+ # On Win32 hosts, provide an option to specify the path to the GnuWin32 tools. -+ if (WIN32 AND NOT CYGWIN) -+ set(LLVM_LIT_TOOLS_DIR "" CACHE PATH "Path to GnuWin32 tools") -+ endif() -+ else() -+ set(LLVM_INCLUDE_TESTS OFF) -+ endif() - -- # Find the LLVM sources and simulate LLVM CMake options. -- include(HandleOutOfTreeLLVM) -+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX}) -+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX}) - else() - set(LLVM_LIT "${CMAKE_SOURCE_DIR}/utils/lit/lit.py") - endif() -@@ -85,8 +153,6 @@ set(LIBUNWIND_TEST_COMPILER_FLAGS "" CACHE STRING - "Additional compiler flags for test programs.") - set(LIBUNWIND_TEST_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/test/lit.site.cfg.in" CACHE STRING - "The Lit testing configuration to use when running the tests.") --set(LIBUNWIND_TEST_PARAMS "" CACHE STRING -- "A list of parameters to run the Lit test suite with.") - - if (NOT LIBUNWIND_ENABLE_SHARED AND NOT LIBUNWIND_ENABLE_STATIC) - message(FATAL_ERROR "libunwind must be built as either a shared or static library.") -@@ -113,6 +179,9 @@ set(CMAKE_MODULE_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/cmake" - ${CMAKE_MODULE_PATH}) - -+set(LIBUNWIND_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -+set(LIBUNWIND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) -+ - if(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR AND NOT APPLE) - set(LIBUNWIND_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}/${LLVM_DEFAULT_TARGET_TRIPLE}/c++) - set(LIBUNWIND_INSTALL_LIBRARY_DIR lib${LLVM_LIBDIR_SUFFIX}/${LLVM_DEFAULT_TARGET_TRIPLE}/c++) diff --git a/deps/unwind.mk b/deps/unwind.mk index 079e4d69b04a3..3951bbf36e22f 100644 --- a/deps/unwind.mk +++ b/deps/unwind.mk @@ -88,40 +88,41 @@ check-unwind: $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-checked LLVMUNWIND_OPTS := $(CMAKE_COMMON) \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DLIBUNWIND_ENABLE_PEDANTIC=OFF \ - -DLLVM_PATH=$(SRCCACHE)/$(LLVM_SRC_DIR)/llvm + -DLIBUNWIND_INCLUDE_DOCS=OFF \ + -DLIBUNWIND_INCLUDE_TESTS=OFF \ + -DLIBUNWIND_INSTALL_HEADERS=ON \ + -DLIBUNWIND_ENABLE_ASSERTIONS=OFF \ + -DLLVM_CONFIG_PATH=$(build_depsbindir)/llvm-config \ + -DLLVM_PATH=$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/llvm -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVMUNWIND_VER)/libunwind-$(LLVMUNWIND_VER).src.tar.xz +$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).tar.xz: | $(SRCCACHE) + $(JLDOWNLOAD) $@ https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVMUNWIND_VER)/llvm-project-$(LLVMUNWIND_VER).src.tar.xz -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz +$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/source-extracted: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).tar.xz $(JLCHECKSUM) $< cd $(dir $<) && $(TAR) xf $< - mv $(SRCCACHE)/libunwind-$(LLVMUNWIND_VER).src $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) + mv $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).src $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER) echo 1 > $@ -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-prologue-epilogue.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted - cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-prologue-epilogue.patch +$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-prologue-epilogue.patch-applied: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/source-extracted + cd $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-prologue-epilogue.patch echo 1 > $@ -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-force-dwarf.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-prologue-epilogue.patch-applied - cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-force-dwarf.patch +$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-force-dwarf.patch-applied: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-prologue-epilogue.patch-applied + cd $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-force-dwarf.patch echo 1 > $@ -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-revert-monorepo-requirement.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-force-dwarf.patch-applied - cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-revert-monorepo-requirement.patch +$(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-force-dwarf.patch-applied + cd $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch echo 1 > $@ -$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-revert-monorepo-requirement.patch-applied - cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch - echo 1 > $@ - -checksum-llvmunwind: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz +checksum-llvmunwind: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).tar.xz $(JLCHECKSUM) $< -$(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied +$(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/source-extracted $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied mkdir -p $(dir $@) cd $(dir $@) && \ - $(CMAKE) $(dir $<) $(LLVMUNWIND_OPTS) + $(CMAKE) $(dir $<)/libunwind $(LLVMUNWIND_OPTS) echo 1 > $@ $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled: $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured @@ -131,7 +132,7 @@ $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled: $(BUILDDIR)/llvmunwind- $(eval $(call staged-install, \ llvmunwind,llvmunwind-$(LLVMUNWIND_VER), \ MAKE_INSTALL,,, \ - cp -fR $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/include/* $(build_includedir))) + cp -fR $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/libunwind/* $(build_includedir))) clean-llvmunwind: -rm -f $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled @@ -139,14 +140,14 @@ clean-llvmunwind: -$(MAKE) -C $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER) clean distclean-llvmunwind: - rm -rf $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz \ + rm -rf $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).tar.xz \ $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) \ $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER) -get-llvmunwind: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz -extract-llvmunwind: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted -configure-llvmunwind: $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured -compile-llvmunwind: $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled +get-llvmunwind: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER).tar.xz +extract-llvmunwind: $(SRCCACHE)/llvm-project-$(LLVMUNWIND_VER)/source-extracted +configure-llvmunwind: $(BUILDDIR)/llvm-project-$(LLVMUNWIND_VER)/build-configured +compile-llvmunwind: $(BUILDDIR)/llvm-project-$(LLVMUNWIND_VER)/build-compiled fastcheck-llvmunwind: check-llvmunwind check-llvmunwind: # no test/check provided by Makefile diff --git a/stdlib/LLVMLibUnwind_jll/Project.toml b/stdlib/LLVMLibUnwind_jll/Project.toml index 36c24111d4d31..0cb0fe5440066 100644 --- a/stdlib/LLVMLibUnwind_jll/Project.toml +++ b/stdlib/LLVMLibUnwind_jll/Project.toml @@ -1,6 +1,6 @@ name = "LLVMLibUnwind_jll" uuid = "47c5dbc3-30ba-59ef-96a6-123e260183d9" -version = "12.0.1+0" +version = "14.0.6+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"