diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index afa95d7dbca36..18328b26c7521 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -1307,7 +1307,6 @@ end function const_prop_call(interp::AbstractInterpreter, mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState, concrete_eval_result::Union{Nothing,ConstCallResult}=nothing) - inf_cache = get_inference_cache(interp) 𝕃ᡒ = typeinf_lattice(interp) forwarded_argtypes = compute_forwarded_argtypes(interp, arginfo, sv) # use `cache_argtypes` that has been constructed for fresh regular inference if available @@ -1318,8 +1317,14 @@ function const_prop_call(interp::AbstractInterpreter, cache_argtypes = matching_cache_argtypes(𝕃ᡒ, mi) end argtypes = matching_cache_argtypes(𝕃ᡒ, mi, forwarded_argtypes, cache_argtypes) - inf_result = cache_lookup(𝕃ᡒ, mi, argtypes, inf_cache) - if inf_result !== nothing + argtypes = get_nospecializeinfer_argtypes(argtypes, cache_argtypes, mi.def::Method) + inf_result = cache_lookup(𝕃ᡒ, mi, argtypes, get_inference_cache(interp)) + if inf_result === missing + # a previous const-prop attempt hit a cycle and produced a limited result; + # don't re-attempt the same work that would lead to the same limited outcome + add_remark!(interp, sv, "[constprop] Found cached but limited constant inference result") + return nothing + elseif inf_result isa InferenceResult # found the cache for this constant prop' if inf_result.result === nothing add_remark!(interp, sv, "[constprop] Found cached constant inference in a cycle") @@ -1356,6 +1361,13 @@ function const_prop_call(interp::AbstractInterpreter, pop!(callstack) return nothing end + if inf_result.tombstone + # This const-prop attempt resolved but hit a cycle and produced a limited result. + # The tombstoned entry is already cached by `promotecache!` via the normal local + # cache mechanism, so `constprop_cache_lookup` will find it on subsequent lookups. + add_remark!(interp, sv, "[constprop] Constant inference produced a limited result") + return nothing + end existing_edge = result.edge inf_result.ci_as_edge = codeinst_as_edge(interp, frame, existing_edge) @assert frame.frameid != 0 && frame.cycleid == frame.frameid diff --git a/Compiler/src/inferenceresult.jl b/Compiler/src/inferenceresult.jl index f2ee4a531d111..43a113fc5d3fa 100644 --- a/Compiler/src/inferenceresult.jl +++ b/Compiler/src/inferenceresult.jl @@ -5,6 +5,25 @@ function matching_cache_argtypes(::AbstractLattice, mi::MethodInstance) return most_general_argtypes(isa(def, Method) ? def : nothing, specTypes) end +# For `@nospecializeinfer` methods, widen the `@nospecialize`'d argument positions back to +# `cache_argtypes` values to respect the `@nospecializeinfer` semantics. +# This also ensures that the constprop `argtypes` have the same length as `cache_argtypes`. +function get_nospecializeinfer_argtypes(argtypes::Vector{Any}, cache_argtypes::Vector{Any}, + method::Method) + is_nospecializeinfer(method) || return argtypes + nargs = Int(method.nargs) + new_argtypes = Vector{Any}(undef, length(cache_argtypes)) + for i = 1:length(cache_argtypes) + i_arg = min(i - 1, nargs - 1) # 0-indexed, 0 is the function slot + if i_arg > 0 && !iszero(method.nospecialize & (1 << (i_arg - 1))) + new_argtypes[i] = cache_argtypes[i] + else + new_argtypes[i] = argtypes[i] + end + end + return new_argtypes +end + struct SimpleArgtypes argtypes::Vector{Any} end @@ -182,8 +201,8 @@ function cache_lookup(𝕃::AbstractLattice, mi::MethodInstance, given_argtypes: cache::Vector{InferenceResult}) method = mi.def::Method nargtypes = length(given_argtypes) + found_tombstone = false for cached_result in cache - cached_result.tombstone && continue # ignore deleted entries (due to LimitedAccuracy) cached_result.linfo === mi || continue cache_argtypes = cached_result.argtypes @assert length(cache_argtypes) == nargtypes "invalid `cache_argtypes` for `mi`" @@ -193,8 +212,15 @@ function cache_lookup(𝕃::AbstractLattice, mi::MethodInstance, given_argtypes: @goto next_cache end end + # Don't return tombstoned entries as cache items: they represent rejected work + # (due to LimitedAccuracy). Instead, record that a tombstone was found so the + # caller can avoid re-attempting the same const-prop that would hit the same limit. + if cached_result.tombstone + found_tombstone = true + @goto next_cache + end return cached_result @label next_cache end - return nothing + return found_tombstone ? missing : nothing end diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 98859efa8c87d..a711bd27b5dc3 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -2988,16 +2988,22 @@ function intrinsic_exct(𝕃::AbstractLattice, f::IntrinsicFunction, argtypes::V return ErrorException end - # fpext, fptrunc, fptoui, fptosi, uitofp, and sitofp have further - # restrictions on the allowed types. + # fpext, sext_int, zext_int, fptrunc, trunc_int, fptoui, fptosi, uitofp, and sitofp + # have further restrictions on the allowed types. if f === Intrinsics.fpext && !(ty <: CORE_FLOAT_TYPES && xty <: CORE_FLOAT_TYPES && Core.sizeof(ty) > Core.sizeof(xty)) return ErrorException end + if (f === Intrinsics.sext_int || f === Intrinsics.zext_int) && !(Core.sizeof(ty) > Core.sizeof(xty)) + return ErrorException + end if f === Intrinsics.fptrunc && !(ty <: CORE_FLOAT_TYPES && xty <: CORE_FLOAT_TYPES && Core.sizeof(ty) < Core.sizeof(xty)) return ErrorException end + if f === Intrinsics.trunc_int && !(Core.sizeof(ty) < Core.sizeof(xty)) + return ErrorException + end if (f === Intrinsics.fptoui || f === Intrinsics.fptosi) && !(xty <: CORE_FLOAT_TYPES) return ErrorException end diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index ed6d37229d9bd..57d3049fea657 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -608,12 +608,14 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter, cycleid:: # A parent may be cached still, but not this intermediate work: # we can throw everything else away now. Caching anything can confuse later # heuristics to consider it worth trying to pursue compiling this further and - # finding infinite work as a result. Avoiding caching helps to ensure there is only - # a finite amount of work that can be discovered later (although potentially still a - # large multiplier on it). + # finding infinite work as a result. Avoiding global caching helps to ensure there + # is only a finite amount of work that can be discovered later (although potentially + # still a large multiplier on it). We still allow local caching so that tombstoned + # entries can be found by `constprop_cache_lookup` to prevent re-attempting the same + # const-prop work that would hit the same limit. result.src = nothing result.tombstone = true - me.cache_mode = CACHE_MODE_NULL + me.cache_mode &= ~CACHE_MODE_GLOBAL set_inlineable!(me.src, false) else # annotate fulltree with type information, diff --git a/Compiler/src/typelimits.jl b/Compiler/src/typelimits.jl index ce4a2a1ccbe91..d69cbf4520ee4 100644 --- a/Compiler/src/typelimits.jl +++ b/Compiler/src/typelimits.jl @@ -338,6 +338,7 @@ end typea === typeb && return true if typea isa PartialStruct aty = widenconst(typea) + issimplertype(𝕃, aty, widenconst(typeb)) || return false if typeb isa Const || typeb isa PartialStruct @assert n_initialized(typea) ≀ n_initialized(typeb) "typeb βŠ‘ typea is assumed" elseif typeb isa PartialStruct diff --git a/Compiler/test/effects.jl b/Compiler/test/effects.jl index cbb77e1d8e2a3..fcd8eb8e88e88 100644 --- a/Compiler/test/effects.jl +++ b/Compiler/test/effects.jl @@ -1493,3 +1493,30 @@ function null_offset(offset) Ptr{UInt8}(C_NULL) + offset end @test null_offset(Int(100)) == Ptr{UInt8}(UInt(100)) + +# https://github.com/JuliaLang/julia/issues/61435 +function catch_error_61435(f, x) + try + f(x) + catch + return :caught + end +end +let f = (x) -> Core.Intrinsics.sext_int(Int16, x) + @test Compiler.is_nothrow(Base.infer_effects(f, (Int8,))) + @test !Compiler.is_nothrow(Base.infer_effects(f, (Int16,))) + @test !Compiler.is_nothrow(Base.infer_effects(f, (Int32,))) + @test catch_error_61435(f, Int16(0)) === :caught +end +let f = (x) -> Core.Intrinsics.zext_int(UInt16, x) + @test Compiler.is_nothrow(Base.infer_effects(f, (UInt8,))) + @test !Compiler.is_nothrow(Base.infer_effects(f, (UInt16,))) + @test !Compiler.is_nothrow(Base.infer_effects(f, (UInt32,))) + @test catch_error_61435(f, UInt16(0)) === :caught +end +let f = (x) -> Core.Intrinsics.trunc_int(Int16, x) + @test !Compiler.is_nothrow(Base.infer_effects(f, (Int8,))) + @test !Compiler.is_nothrow(Base.infer_effects(f, (Int16,))) + @test Compiler.is_nothrow(Base.infer_effects(f, (Int32,))) + @test catch_error_61435(f, Int16(0)) === :caught +end diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index 89b7de36fb26a..6b57981a72e6d 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -5077,6 +5077,22 @@ let 𝕃ᡒ = Compiler.fallback_lattice @test t.fields == Any[Const(42), Int] end +# issue #60715 +let 𝕃 = Compiler.fallback_lattice + local fn, fn1, pn, pn1 + F(n) = iszero(n) ? Union{} : Tuple{Int, Union{Int, F(n-1)}} + P(n) = (f = F(n); Compiler.PartialStruct(𝕃, f, [Const(0), fieldtype(f, 2)])) + + n = 0 + while Compiler.issimpleenoughtype(F(n+1)) + n += 1 + fn, fn1, pn, pn1 = F(n), F(n+1), P(n), P(n+1) + @test Compiler.:βŠ‘(𝕃, pn, pn1) + end + @test !Compiler.issimplertype(𝕃, pn1, pn) + @test !isa(Compiler.tmerge(𝕃, pn, pn1), Compiler.PartialStruct) +end + foo_empty_vararg(i...) = i[2] bar_empty_vararg(i) = foo_empty_vararg(10, 20, 30, i...) @test bar_empty_vararg(Union{}[]) === 20 @@ -6545,4 +6561,24 @@ function issue60883() end @test issue60883() === true +# issue #60715 +let + f() = 1; f(_, x...) = (0, f(x...)) + @test f(1, 2, 3) == (0, (0, (0, 1))) +end + +# aviatesk/JETLS.jl/issues/618 +Base.@nospecializeinfer function jetls618(a, @nospecialize(rest...)) + if a > 0 + z = a + length(rest) + else + z = 0 + end + println(z) + return z +end +@test Base.infer_return_type() do + jetls618(1,2,3), jetls618(1,2,3,4) +end == Tuple{Int,Int} + end # module inference diff --git a/Compiler/test/inline.jl b/Compiler/test/inline.jl index bde710a3ef9c0..1d71aa5638e8c 100644 --- a/Compiler/test/inline.jl +++ b/Compiler/test/inline.jl @@ -151,8 +151,14 @@ end end (src, _) = only(code_typed(sum27403, Tuple{Vector{Int}})) + is_bounds_throw_invoke_target(@nospecialize(callee)) = + callee === Base.throw_boundserror || + callee == Core.GlobalRef(Base, :throw_boundserror) || + callee == Core.GlobalRef(Base, :_throw_boundserror_indices) || + (callee isa Core.MethodInstance && (callee.def.def.name === :throw_boundserror || + callee.def.def.name === :_throw_boundserror_indices)) @test !any(src.code) do x - x isa Expr && x.head === :invoke && !(x.args[2] in (Core.GlobalRef(Base, :throw_boundserror), Base.throw_boundserror)) + x isa Expr && x.head === :invoke && !is_bounds_throw_invoke_target(x.args[2]) end end diff --git a/NEWS.md b/NEWS.md index dc596460b9d39..8a2d55bacf9e5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -110,11 +110,6 @@ Deprecated or removed * The method `merge(combine::Callable, d::AbstractDict...)` is now deprecated to favor `mergewith` instead ([#59775]). -Deprecated or removed ---------------------- - -* The method `merge(combine::Callable, d::AbstractDict...)` is now deprecated to favor `mergewith` instead ([#59775]). - [#47102]: https://github.com/JuliaLang/julia/issues/47102 [#48507]: https://github.com/JuliaLang/julia/issues/48507 diff --git a/base/abstractarray.jl b/base/abstractarray.jl index a451bf56cce97..1ff11a345e82d 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -725,7 +725,7 @@ See also [`checkbounds`](@ref). """ function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{Any, Vararg}) @inline - return checkindex(Bool, get(inds, 1, OneTo(1)), I[1])::Bool & + return checkindex(Bool, get(inds, 1, OneTo(1)), I[1])::Bool && checkbounds_indices(Bool, safe_tail(inds), tail(I)) end @@ -2394,9 +2394,7 @@ end function _typed_hvncat(T::Type, ::Val{N}, xs::Number...) where N N < 0 && throw(ArgumentError("concatenation dimension must be non-negative")) - A = cat_similar(xs[1], T, (ntuple(Returns(1), Val(N - 1))..., length(xs))) - hvncat_fill!(A, false, xs) - return A + return reshape(T[xs...], (ntuple(Returns(1), Val(N - 1))..., length(xs))) end function _typed_hvncat(::Type{T}, ::Val{N}, as::AbstractArray...) where {T, N} @@ -2720,12 +2718,13 @@ function _typed_hvncat_shape(::Type{T}, shape::NTuple{N, Tuple}, row_first, as:: # copy into final array A = cat_similar(as[1], T, ntuple(i -> outdims[i], nd)) - hvncat_fill!(A, currentdims, blockcounts, d1, d2, as) + if !any(iszero, outdims) + hvncat_fill!(A, currentdims, blockcounts, d1, d2, as) + end return A end -function hvncat_fill!(A::AbstractArray{T, N}, scratch1::Vector{Int}, scratch2::Vector{Int}, - d1::Int, d2::Int, as::Tuple) where {T, N} +function hvncat_fill!(A::AbstractArray{T, N}, scratch1::Vector{Int}, scratch2::Vector{Int}, d1::Int, d2::Int, as::Tuple) where {T, N} N > 1 || throw(ArgumentError("dimensions of the destination array must be at least 2")) length(scratch1) == length(scratch2) == N || throw(ArgumentError("scratch vectors must have as many elements as the destination array has dimensions")) @@ -2733,42 +2732,46 @@ function hvncat_fill!(A::AbstractArray{T, N}, scratch1::Vector{Int}, scratch2::V 0 < d2 < 3 && d1 != d2 || throw(ArgumentError("d1 and d2 must be either 1 or 2, exclusive.")) - outdims = size(A) + outdimsprod = cumprod(size(A)) offsets = scratch1 inneroffsets = scratch2 for a ∈ as + startindex = CartesianIndex(ntuple(i -> offsets[i] + 1, Val(N))) if isa(a, AbstractArray) - for ai ∈ a - @inbounds Ai = hvncat_calcindex(offsets, inneroffsets, outdims, N) - A[Ai] = ai - - @inbounds for j ∈ 1:N - inneroffsets[j] += 1 - inneroffsets[j] < cat_size(a, j) && break - inneroffsets[j] = 0 + if !isempty(a) + if length(a) > 4 + endindex = CartesianIndex(ntuple(i -> offsets[i] + cat_size(a, i), Val(N))) + @inbounds A[startindex:endindex] = a + else + for ai ∈ a + @inbounds Ai = hvncat_calcindex(offsets, inneroffsets, outdimsprod, N) + @inbounds A[Ai] = ai + @inbounds for j ∈ 1:N + inneroffsets[j] += 1 + inneroffsets[j] < cat_size(a, j) && break + inneroffsets[j] = 0 + end + end end end else - @inbounds Ai = hvncat_calcindex(offsets, inneroffsets, outdims, N) - A[Ai] = a + @inbounds A[startindex] = a end - @inbounds for j ∈ (d1, d2, 3:N...) - offsets[j] += cat_size(a, j) - offsets[j] < outdims[j] && break - offsets[j] = 0 + @inbounds for i ∈ (d1, d2, 3:N...) + offsets[i] += cat_size(a, i) + offsets[i] < cat_size(A, i) && break + offsets[i] = 0 end end end @propagate_inbounds function hvncat_calcindex(offsets::Vector{Int}, inneroffsets::Vector{Int}, - outdims::Tuple{Vararg{Int}}, nd::Int) + outdimsprod::NTuple{N, Int}, nd::Int) where {N} Ai = inneroffsets[1] + offsets[1] + 1 for j ∈ 2:nd increment = inneroffsets[j] + offsets[j] - for k ∈ 1:j-1 - increment *= outdims[k] - end + increment *= outdimsprod[j - 1] Ai += increment end Ai @@ -3490,7 +3493,7 @@ julia> map!(+, zeros(Int, 5), 100:999, 1:3) ``` """ function map!(f::F, dest::AbstractArray, As::AbstractArray...) where {F} - @assert !isempty(As) # should dispatch to map!(f, A) + @assert !isempty(As) "should dispatch to map!(f, A)" map_n!(f, dest, As) end diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 31c00c5b4f8d5..f044264e327b0 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -232,6 +232,7 @@ function merge!(d::AbstractDict, others::AbstractDict...) end return d end +typeof(merge!).name.max_methods = UInt8(1) """ mergewith!(combine, d::AbstractDict, others::AbstractDict...) -> d @@ -282,6 +283,7 @@ Dict{Int64, Int64} with 3 entries: function mergewith!(combine, d::AbstractDict, others::AbstractDict...) foldl(mergewith!(combine), others; init = d) end +typeof(mergewith!).name.max_methods = UInt8(1) function mergewith!(combine, d1::AbstractDict, d2::AbstractDict) for (k, v) in d2 @@ -358,6 +360,7 @@ Dict{String, Float64} with 3 entries: """ merge(d::AbstractDict, others::AbstractDict...) = merge!(_typeddict(d, others...), others...) +typeof(merge).name.max_methods = UInt8(1) """ mergewith(combine, d::AbstractDict, others::AbstractDict...) @@ -405,6 +408,7 @@ Dict{Any, Any} with 1 entry: mergewith(combine, d::AbstractDict, others::AbstractDict...) = mergewith!(combine, _typeddict(d, others...), others...) mergewith(combine) = (args...) -> mergewith(combine, args...) +typeof(mergewith).name.max_methods = UInt8(1) merge(combine::Callable, d::AbstractDict, others::AbstractDict...) = merge!(combine, _typeddict(d, others...), others...) diff --git a/base/array.jl b/base/array.jl index 7cefac20fb377..38b81ef702311 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2326,7 +2326,7 @@ function vcat(arrays::Vector{T}...) where T nd = 1 for a in arrays na = length(a) - @assert nd + na <= 1 + length(arr) # Concurrent modification of arrays? + @assert nd + na <= 1 + length(arr) "Concurrent modification of arrays?" unsafe_copyto!(arr, nd, a, 1, na) nd += na end diff --git a/base/arrayshow.jl b/base/arrayshow.jl index 903fb4f6b68bc..b456449ceee14 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -183,7 +183,7 @@ function _print_matrix(io, @nospecialize(X::AbstractVecOrMat), pre, sep, post, h screenwidth -= length(pre)::Int + length(post)::Int presp = repeat(" ", length(pre)::Int) # indent each row to match pre string postsp = "" - @assert textwidth(hdots) == textwidth(ddots) + @assert textwidth(hdots) == textwidth(ddots) "hdots and ddots must have same textwidth" sepsize = length(sep)::Int m, n = length(rowsA), length(colsA) # To figure out alignments, only need to look at as many rows as could @@ -421,7 +421,7 @@ _show_nonempty(io::IO, X::AbstractMatrix, prefix::String) = _show_nonempty(io, inferencebarrier(X), prefix, false, axes(X)) function _show_nonempty(io::IO, @nospecialize(X::AbstractMatrix), prefix::String, drop_brackets::Bool, axs::Tuple{AbstractUnitRange,AbstractUnitRange}) - @assert !isempty(X) + @assert !isempty(X) "X should be non-empty" limit = get(io, :limit, false)::Bool indr, indc = axs nr, nc = length(indr), length(indc) diff --git a/base/asyncevent.jl b/base/asyncevent.jl index a4a82b4aba120..b08cf7934fe43 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -126,13 +126,13 @@ mutable struct Timer associate_julia_struct(this.handle, this) iolock_begin() err = ccall(:uv_timer_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}), loop, this) - @assert err == 0 + @assert err == 0 "failed to initialize timer" finalizer(uvfinalize, this) ccall(:uv_update_time, Cvoid, (Ptr{Cvoid},), loop) err = ccall(:uv_timer_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64, UInt64), this, @cfunction(uv_timercb, Cvoid, (Ptr{Cvoid},)), timeoutms, intervalms) - @assert err == 0 + @assert err == 0 "failed to start timer" iolock_end() return this end diff --git a/base/bitarray.jl b/base/bitarray.jl index 04565411cabfb..45bcb8d772713 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -325,7 +325,7 @@ function copy_to_bitarray_chunks!(Bc::Vector{UInt64}, pos_d::Int, C::Array{Bool} bind += 1 end @inbounds if bind ≀ kd1 - @assert bind == kd1 + @assert bind == kd1 "bind != kd1" c = UInt64(0) for j = 0:ld1 c |= (UInt64(C[ind]) << j) diff --git a/base/broadcast.jl b/base/broadcast.jl index bbdb296515b66..0b185408d6d32 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -630,11 +630,15 @@ to_index(::Tuple{}) = CartesianIndex() to_index(Is::Tuple{Any}) = Is[1] to_index(Is::Tuple) = CartesianIndex(Is) -@inline Base.checkbounds(bc::Broadcasted, I::CartesianIndex) = - Base.checkbounds_indices(Bool, axes(bc), (I,)) || Base.throw_boundserror(bc, (I,)) +@inline function Base.checkbounds(bc::Broadcasted, I::CartesianIndex) + Base.checkbounds_indices(Bool, axes(bc), (I,)) || Base.throw_boundserror(bc, I) + nothing +end -@inline Base.checkbounds(bc::Broadcasted, I::Integer) = - Base.checkindex(Bool, eachindex(IndexLinear(), bc), I) || Base.throw_boundserror(bc, (I,)) +@inline function Base.checkbounds(bc::Broadcasted, I::Integer) + Base.checkindex(Bool, eachindex(IndexLinear(), bc), I) || Base.throw_boundserror(bc, I) + nothing +end """ diff --git a/base/channels.jl b/base/channels.jl index 5b57a6202dc2f..42ab075e748cf 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -345,8 +345,8 @@ of type `Channel{Any}(0)`. Returns a tuple, `(Array{Channel}, Array{Task})`, of the created channels and tasks. """ function channeled_tasks(n::Int, funcs...; ctypes=fill(Any,n), csizes=fill(0,n)) - @assert length(csizes) == n - @assert length(ctypes) == n + @assert length(csizes) == n "length(csizes) != n" + @assert length(ctypes) == n "length(ctypes) != n" chnls = map(i -> Channel{ctypes[i]}(csizes[i]), 1:n) tasks = Task[ Task(() -> f(chnls...)) for f in funcs ] diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 5180f830ce187..575b43d4980d8 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -44,7 +44,7 @@ end # Basic functions for working with permutations @inline function _foldoneto(op, acc, ::Val{N}) where N - @assert N::Integer > 0 + @assert N::Integer > 0 "N must be positive" if @generated quote acc_0 = acc diff --git a/base/essentials.jl b/base/essentials.jl index 2f6faadea08a6..0729fb6ab1418 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -10,7 +10,11 @@ const Bottom = Union{} size(a::Array) = getfield(a, :size) length(t::AbstractArray) = (@inline; prod(size(t))) size(a::GenericMemory) = (getfield(a, :length),) +throw_boundserror(A) = (@noinline; throw(BoundsError(A, ()))) throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I))) +throw_boundserror(A, i1, i2, I...) = (@noinline; throw(BoundsError(A, (i1, i2, I...)))) +_throw_boundserror_indices(A) = (@noinline; throw(BoundsError(A, ()))) +_throw_boundserror_indices(A, i1, I...) = (@noinline; throw(BoundsError(A, (i1, I...)))) # multidimensional getindex will be defined later on @@ -383,6 +387,7 @@ end function checkbounds(A::Union{Array, GenericMemory}, i::Int) @inline checkbounds(Bool, A, i) || throw_boundserror(A, (i,)) + nothing end default_access_order(::GenericMemory{:not_atomic}) = :not_atomic diff --git a/base/file.jl b/base/file.jl index f5e0ad2e9dc6f..f645c87966a7e 100644 --- a/base/file.jl +++ b/base/file.jl @@ -769,7 +769,7 @@ function _win_mkstemp(temppath::AbstractString) tempp, temppfx, UInt32(0), tname) windowserror("GetTempFileName", uunique == 0) lentname = something(findfirst(iszero, tname)) - @assert lentname > 0 + @assert lentname > 0 "unexpected index" resize!(tname, lentname - 1) return transcode(String, tname) end @@ -1403,7 +1403,7 @@ function readlink(path::AbstractString) if ret < 0 uv_fs_req_cleanup(req) uv_error("readlink($(repr(path)))", ret) - @assert false + @assert false "unexpected uv readlink error" end tgt = unsafe_string(ccall(:jl_uv_fs_t_ptr, Cstring, (Ptr{Cvoid},), req)) uv_fs_req_cleanup(req) diff --git a/base/filesystem.jl b/base/filesystem.jl index 36d60f1d3318a..c0abbdfd03b6d 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -268,7 +268,7 @@ function read(f::File, ::Type{UInt8}) ret = ccall(:jl_fs_read, Int32, (OS_HANDLE, Ptr{Cvoid}, Csize_t), f.handle, p, 1) uv_error("read", ret) - @assert ret <= sizeof(p) == 1 + @assert ret <= sizeof(p) == 1 "unexpected read size" ret < 1 && throw(EOFError()) return p[] % UInt8 end diff --git a/base/genericmemory.jl b/base/genericmemory.jl index b34095ea37d48..5a1e4d8d22d1e 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -272,7 +272,7 @@ end function setindex!(A::Memory{T}, x, i1::Int, i2::Int, I::Int...) where {T} @inline - @boundscheck (i2 == 1 && all(==(1), I)) || throw_boundserror(A, (i1, i2, I...)) + @boundscheck (i2 == 1 && all(==(1), I)) || throw_boundserror(A, i1, i2, I...) setindex!(A, x, i1) end diff --git a/base/gmp.jl b/base/gmp.jl index ee8e620603e9d..748c256cc2401 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -261,7 +261,7 @@ function export!(a::AbstractVector{T}, n::BigInt; order::Integer=-1, nails::Inte count = Ref{Csize_t}() ccall((:__gmpz_export, libgmp), Ptr{T}, (Ptr{T}, Ref{Csize_t}, Cint, Csize_t, Cint, Csize_t, mpz_t), a, count, order, sizeof(T), endian, nails, n) - @assert count[] ≀ length(a) + @assert count[] ≀ length(a) "count[] > length(a)" return a, Int(count[]) end diff --git a/base/hamt.jl b/base/hamt.jl index c77c592b17e58..ff076651a9f4b 100644 --- a/base/hamt.jl +++ b/base/hamt.jl @@ -199,7 +199,7 @@ or grows the HAMT by inserting a new trie instead. end set!(trie, bi) else - @assert present + @assert present "!found && !present" # collision -> grow leaf = @inbounds trie.data[i]::Leaf{K,V} leaf_h = HashState(h, leaf.key) diff --git a/base/idset.jl b/base/idset.jl index 59b47dee64a04..e8d14a785c5bb 100644 --- a/base/idset.jl +++ b/base/idset.jl @@ -52,7 +52,7 @@ function push!(s::IdSet, @nospecialize(x)) else if s.max < length(s.list) idx = s.max - @assert !isassigned(s.list, idx + 1) + @assert !isassigned(s.list, idx + 1) "bucket is already occupied" s.list[idx + 1] = x s.max = idx + 1 else @@ -61,7 +61,7 @@ function push!(s::IdSet, @nospecialize(x)) idx = newidx[] s.max = idx < 0 ? -idx : idx + 1 end - @assert s.list[s.max] === x + @assert s.list[s.max] === x "unexpected object in bucket" setfield!(s, :idxs, ccall(:jl_idset_put_idx, Any, (Any, Any, Int), s.list, s.idxs, idx)) s.count += 1 end diff --git a/base/indices.jl b/base/indices.jl index 0d0e56b12be4b..7965cb6e42152 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -215,6 +215,7 @@ end # those are the permutations that preserve the order of the non-singleton # dimensions. function setindex_shape_check(X::AbstractArray, I::Integer...) + @inline li = ndims(X) lj = length(I) i = j = 1 diff --git a/base/iostream.jl b/base/iostream.jl index c58818968c0b1..97d2f2645d785 100644 --- a/base/iostream.jl +++ b/base/iostream.jl @@ -504,7 +504,7 @@ function copyuntil(out::IOBuffer, s::IOStream, delim::UInt8; keep::Bool=false) (eof(s) || len == out.maxsize) && break len = min(2len + 64, out.maxsize) ensureroom(out, len) - @assert length(out.data) >= len + @assert length(out.data) >= len "length(out.data) < len" end return out end diff --git a/base/loading.jl b/base/loading.jl index 3bed81b6eb78d..cbc9640a5ef59 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1254,7 +1254,7 @@ function find_all_in_cache_path(pkg::PkgId, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT if iszero(isvalid_cache_header(io)) false else - _, _, _, _, _, _, _, flags = parse_cache_header(io, path) + _, _, _, _, _, _, flags = parse_cache_header(io, path) CacheFlags(flags).use_pkgimages end finally @@ -2027,7 +2027,7 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union io = open(path, "r") try iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.") - _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path) + _, (includes, _, _), depmodnames, _, _, clone_targets, _ = parse_cache_header(io, path) pkgimage = !isempty(clone_targets) @@ -3274,7 +3274,7 @@ function compilecache_dir(pkg::PkgId) return joinpath(DEPOT_PATH[1], entrypath) end -function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String +function compilecache_path(pkg::PkgId, prefs_blob::String; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String entrypath, entryfile = cache_file_entry(pkg) cachepath = joinpath(DEPOT_PATH[1], entrypath) isdir(cachepath) || mkpath(cachepath) @@ -3291,8 +3291,7 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=Cac cpu_target = unsafe_string(JLOptions().cpu_target) end crc = _crc32c(cpu_target, crc) - - crc = _crc32c(prefs_hash, crc) + crc = _crc32c(prefs_blob, crc) project_precompile_slug = slug(crc, 5) abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji")) end @@ -3363,10 +3362,10 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in Linking.link_image(tmppath_o, tmppath_so) end - # Read preferences hash back from .ji file (we can't precompute because - # we don't actually know what the list of compile-time preferences are without compiling) - prefs_hash = preferences_hash(tmppath) - cachefile = compilecache_path(pkg, prefs_hash; flags=cacheflags) + # Read preferences blob back from .ji file (we can't precompute because we don't + # actually know what the list of compile-time preferences are without compiling) + prefs_blob = preferences_blob(tmppath) + cachefile = compilecache_path(pkg, prefs_blob; flags=cacheflags) ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing # append checksum for so to the end of the .ji file: @@ -3596,18 +3595,10 @@ function _parse_cache_header(f::IO, cachefile::AbstractString) push!(includes, CacheHeaderIncludes(modkey, depname, fsize, hash, mtime, modpath)) end end - prefs = String[] - while true - n2 = read(f, Int32) - totbytes -= 4 - if n2 == 0 - break - end - push!(prefs, String(read(f, n2))) - totbytes -= n2 - end - prefs_hash = read(f, UInt64) - totbytes -= 8 + n2 = read(f, Int32) + totbytes -= 4 + prefs_blob = String(read(f, n2)) + totbytes -= n2 srctextpos = read(f, Int64) totbytes -= 8 @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))" @@ -3618,12 +3609,12 @@ function _parse_cache_header(f::IO, cachefile::AbstractString) srcfiles = srctext_files(f, srctextpos, includes) - return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags + return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs_blob, clone_targets, flags end function parse_cache_header(f::IO, cachefile::AbstractString) modules, (includes, srcfiles, requires), required_modules, - srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile) + srctextpos, prefs_blob, clone_targets, flags = _parse_cache_header(f, cachefile) includes_srcfiles = CacheHeaderIncludes[] includes_depfiles = CacheHeaderIncludes[] @@ -3697,7 +3688,7 @@ function parse_cache_header(f::IO, cachefile::AbstractString) end end - return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags + return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs_blob, clone_targets, flags end function parse_cache_header(cachefile::String) @@ -3711,14 +3702,14 @@ function parse_cache_header(cachefile::String) end end -preferences_hash(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[6] -function preferences_hash(cachefile::String) +preferences_blob(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[5] +function preferences_blob(cachefile::String) io = open(cachefile, "r") try if iszero(isvalid_cache_header(io)) throw(ArgumentError("Incompatible header in cache file $cachefile.")) end - return preferences_hash(io, cachefile) + return preferences_blob(io, cachefile) finally close(io) end @@ -3740,7 +3731,7 @@ function cache_dependencies(cachefile::String) end function read_dependency_src(io::IO, cachefile::AbstractString, filename::AbstractString) - _, (includes, _, _), _, srctextpos, _, _, _, _ = parse_cache_header(io, cachefile) + _, (includes, _, _), _, srctextpos, _, _, _ = parse_cache_header(io, cachefile) srctextpos == 0 && error("no source-text stored in cache file") seek(io, srctextpos) return _read_dependency_src(io, filename, includes) @@ -3940,42 +3931,48 @@ function get_preferences(uuid::Union{UUID,Nothing} = nothing) return merged_prefs end -function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{String}) - # Start from a predictable hash point to ensure that the same preferences always - # hash to the same value, modulo changes in how Dictionaries are hashed. - h = UInt(0) - uuid === nothing && return UInt64(h) - - # Load the preferences - prefs = get_preferences(uuid) - - # Walk through each name that's called out as a compile-time preference - for name in prefs_list - prefs_value = get(prefs, name, nothing) - if prefs_value !== nothing - h = hash(prefs_value, h)::UInt - end - end - # We always return a `UInt64` so that our serialization format is stable - return UInt64(h) -end - -get_preferences_hash(m::Module, prefs_list::Vector{String}) = get_preferences_hash(PkgId(m).uuid, prefs_list) - # This is how we keep track of who is using what preferences at compile-time const COMPILETIME_PREFERENCES = Dict{UUID,Set{String}}() -# In `Preferences.jl`, if someone calls `load_preference(@__MODULE__, key)` while we're precompiling, -# we mark that usage as a usage at compile-time and call this method, so that at the end of `.ji` generation, -# we can record the list of compile-time preferences and embed that into the `.ji` header +# This is used by `Preferences.load_preference` to record any (pre)compile-time preference +# queries, which the resulting pkgimage will be keyed on to detect preference changes. function record_compiletime_preference(uuid::UUID, key::String) pref = get!(Set{String}, COMPILETIME_PREFERENCES, uuid) push!(pref, key) return nothing end -get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIME_PREFERENCES, uuid)) -get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid) -get_compiletime_preferences(::Nothing) = String[] + +# Return a serialized "blob" of all observed (at compile-time) preferences. +# This is called at pkgimage write time (.ji generation). +function get_preferences_blob() + isempty(COMPILETIME_PREFERENCES) && return "" + observed = Dict{String, Any}() + unset = Dict{String, Any}() + for (uuid, pref_keys) in COMPILETIME_PREFERENCES + uuid_prefs = get_preferences(uuid) + uuid_str = string(uuid) + for key in pref_keys + if haskey(uuid_prefs, key) + uuid_observed = get!(Dict{String, Any}, observed, uuid_str) + uuid_observed[key] = uuid_prefs[key] + else + # preferences that were not set are tracked in a + # separate table to prevent name / value collisions + uuid_unset = get!(Set{Any}, unset, uuid_str) + push!(uuid_unset, key) + end + end + end + for (uuid, uuid_unset) in pairs(unset) + unset[uuid] = sort!(collect(uuid_unset)) + end + if !isempty(unset) + observed["unset"] = unset + end + buf = IOBuffer() + TOML.Printer.print(buf, observed; sorted=true) + return String(take!(buf)) +end function check_clone_targets(clone_targets) rejection_reason = ccall(:jl_check_pkgimage_clones, Any, (Ptr{Cchar},), clone_targets) @@ -3989,10 +3986,10 @@ global mkpidlock_hook::Any global trymkpidlock_hook::Any global parse_pidfile_hook::Any -# The preferences hash is only known after precompilation so just assume no preferences. +# The preferences blob is only known after precompilation so just assume no preferences. # Also ignore the active project, which means that if all other conditions are equal, # the same package cannot be precompiled from different projects and/or different preferences at the same time. -compilecache_pidfile_path(pkg::PkgId; flags::CacheFlags=CacheFlags()) = compilecache_path(pkg, UInt64(0); project="", flags) * ".pidfile" +compilecache_pidfile_path(pkg::PkgId; flags::CacheFlags=CacheFlags()) = compilecache_path(pkg, ""; project="", flags) * ".pidfile" const compilecache_pidlock_stale_age = 10 @@ -4087,6 +4084,54 @@ function any_includes_stale(includes::Vector{CacheHeaderIncludes}, cachefile::St return false end +# This custom equality predicate is analogous to `===`, except that it also +# compares mutable containers structurally. This allows us to differentiate +# `1.0` / `1` / `true` in Preferences, despite these being normally `isequal` +toml_egal(@nospecialize(A), @nospecialize(B)) = A === B + +function toml_egal(A::AbstractArray, B::AbstractArray) + axes(A) != axes(B) && return false + for (a, b) in zip(A, B) + !toml_egal(a, b) && return false + end + return true +end + +function toml_egal(A::AbstractDict, B::AbstractDict) + isa(A,IdDict) != isa(B,IdDict) && return false + length(A) != length(B) && return false + for pair in A + !in(pair, B, toml_egal) && return false + end + return true +end + +function stale_prefs(prefs_blob::String) + # ensure any preferences observed match their precompile-time values + prefs_blob == "" && return false # no observed preferences (fast-path) + prefs_data = TOML.parse(TOML.Parser{nothing}(prefs_blob)) + + for (uuid, observed) in prefs_data + uuid == "unset" && continue + curr = get_preferences(UUID(uuid)) + for (key, val) in observed + # any set preferences should have the same value + !haskey(curr, key) && return true + !toml_egal(curr[key], val) && return true + end + end + if haskey(prefs_data, "unset") + for (uuid, observed) in prefs_data["unset"] + curr = get_preferences(UUID(uuid)) + for key in observed + # any unset preferences should still be unset + haskey(curr, key) && return true + end + end + end + return false +end + # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check @constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false, requested_flags::CacheFlags=CacheFlags(), reasons=nothing) @@ -4110,7 +4155,7 @@ end record_reason(reasons, "different Julia build configuration") return true # incompatible cache file end - modules, (includes, _, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, actual_flags = parse_cache_header(io, cachefile) + modules, (includes, _, requires), required_modules, srctextpos, prefs_blob, clone_targets, actual_flags = parse_cache_header(io, cachefile) if isempty(modules) return true # ignore empty file end @@ -4266,9 +4311,8 @@ end end end - curr_prefs_hash = get_preferences_hash(id.uuid, prefs) - if prefs_hash != curr_prefs_hash - @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))" + if stale_prefs(prefs_blob) + @debug "Rejecting cache file $cachefile because preferences have changed" record_reason(reasons, "package preferences changed") return true end diff --git a/base/mpfr.jl b/base/mpfr.jl index f17ac72c7e198..83c62681ca113 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -1254,7 +1254,7 @@ function _prettify_bigfloat(s::String)::String else neg = startswith(int, '-') neg == true && (int = lstrip(int, '-')) - @assert length(int) == 1 + @assert length(int) == 1 "length(int) != 1" string(neg ? '-' : "", '0', '.', '0'^(-expo-1), int, frac == "0" ? "" : frac) end else diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 28da821f5ee79..4c699a26750fe 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -759,12 +759,12 @@ end # Here we try to consume N of the indices (if there are that many available) @inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{CartesianIndex,Vararg}) inds1, rest = IteratorsMD.split(inds, Val(length(I[1]))) - checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) + checkindex(Bool, inds1, I[1]) && checkbounds_indices(Bool, rest, tail(I)) 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))) + 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, @@ -781,7 +781,7 @@ end # Here we try to consume N of the indices (if there are that many available) @inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg}) where N inds1, rest = IteratorsMD.split(inds, Val(N)) - checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) + checkindex(Bool, inds1, I[1]) && checkbounds_indices(Bool, rest, tail(I)) end @inline checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndices) = checkbounds_indices(Bool, inds, I.indices) @@ -907,12 +907,12 @@ checkbounds(::Type{Bool}, A::AbstractArray, i::AbstractVector{Bool}) = checkindex(Bool, eachindex(IndexLinear(), A), i) @inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{AbstractArray{Bool},Vararg}) inds1, rest = IteratorsMD.split(inds, Val(ndims(I[1]))) - checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) + checkindex(Bool, inds1, I[1]) && checkbounds_indices(Bool, rest, tail(I)) end checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractVector{Bool}) = axes1(I) == inds checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractRange{Bool}) = axes1(I) == inds checkindex(::Type{Bool}, inds::Tuple, I::AbstractArray{Bool}) = _check_boolean_axes(inds, axes(I)) -_check_boolean_axes(inds::Tuple, axes::Tuple) = (inds[1] == axes[1]) & _check_boolean_axes(tail(inds), tail(axes)) +_check_boolean_axes(inds::Tuple, axes::Tuple) = (inds[1] == axes[1]) && _check_boolean_axes(tail(inds), tail(axes)) _check_boolean_axes(::Tuple{}, axes::Tuple) = all(==(OneTo(1)), axes) ensure_indexable(I::Tuple{}) = () @@ -1488,7 +1488,7 @@ function copy_to_bitarray_chunks!(Bc::Vector{UInt64}, pos_d::Int, C::StridedArra end @inbounds if bind ≀ kd1 - @assert bind == kd1 + @assert bind == kd1 "bind != kd1" c = UInt64(0) for j = 0:ld1 c |= (UInt64(unchecked_bool_convert(C[ind])) << j) diff --git a/base/partr.jl b/base/partr.jl index d488330f0c87e..0716b9dfe9ee7 100644 --- a/base/partr.jl +++ b/base/partr.jl @@ -139,7 +139,7 @@ end function multiq_insert(task::Task, priority::UInt16) tpid = ccall(:jl_get_task_threadpoolid, Int8, (Any,), task) - @assert tpid > -1 + @assert tpid > -1 "invalid tpid" heap_p = multiq_size(tpid) tp = tpid + 1 diff --git a/base/pkgid.jl b/base/pkgid.jl index 7ef7c58eee4cc..580f52ffbe17b 100644 --- a/base/pkgid.jl +++ b/base/pkgid.jl @@ -38,7 +38,7 @@ end function binunpack(s::String) io = IOBuffer(s) z = read(io, UInt8) - @assert z === 0x00 + @assert z === 0x00 "unexpected data" uuid = read(io, UInt128) name = read(io, String) return PkgId(UUID(uuid), name) diff --git a/base/precompilation.jl b/base/precompilation.jl index 3c6c6fa996f06..6650034344088 100644 --- a/base/precompilation.jl +++ b/base/precompilation.jl @@ -2,18 +2,19 @@ module Precompilation using Base: CoreLogging, PkgId, UUID, SHA1, parsed_toml, project_file_name_uuid, project_names, project_file_manifest_path, get_deps, preferences_names, isaccessibledir, isfile_casesensitive, - base_project, isdefined + base_project, env_project_file, isdefined # This is currently only used for pkgprecompile but the plan is to use this in code loading in the future # see the `kc/codeloading2.0` branch struct ExplicitEnv path::String - project_deps::Dict{String, UUID} # [deps] in Project.toml - project_weakdeps::Dict{String, UUID} # [weakdeps] in Project.toml - project_extras::Dict{String, UUID} # [extras] in Project.toml - project_extensions::Dict{String, Vector{UUID}} # [exts] in Project.toml - deps::Dict{UUID, Vector{UUID}} # all dependencies in Manifest.toml - weakdeps::Dict{UUID, Vector{UUID}} # all weak dependencies in Manifest.toml + project_deps::Dict{String, UUID} # [deps] in the active project's Project.toml + project_weakdeps::Dict{String, UUID} # [weakdeps] in the active project's Project.toml + project_extras::Dict{String, UUID} # [extras] in the active project's Project.toml + project_extensions::Dict{String, Vector{UUID}} # [extensions] in the active project's Project.toml + workspace_deps::Dict{String, UUID} # union of [deps] from all workspace member Project.tomls + deps::Dict{UUID, Vector{UUID}} # full dependency graph from Manifest.toml + weakdeps::Dict{UUID, Vector{UUID}} # full weak dependency graph from Manifest.toml extensions::Dict{UUID, Dict{String, Vector{UUID}}} # Lookup name for a UUID names::Dict{UUID, String} @@ -33,6 +34,7 @@ function ExplicitEnv(::Nothing, envpath::String="") Dict{String, UUID}(), # project_weakdeps Dict{String, UUID}(), # project_extras Dict{String, Vector{UUID}}(), # project_extensions + Dict{String, UUID}(), # workspace_deps Dict{UUID, Vector{UUID}}(), # deps Dict{UUID, Vector{UUID}}(), # weakdeps Dict{UUID, Dict{String, Vector{UUID}}}(), # extensions @@ -260,8 +262,38 @@ function ExplicitEnv(envpath::String) end =# + # Collect the union of [deps] from all workspace member projects. + # For non-workspace projects, this is the same as project_deps. + workspace_deps = copy(project_deps) + base = base_project(envpath) + if base !== nothing + base_d = parsed_toml(base) + # Add deps from the workspace root project + for (name, _uuid) in get(Dict{String, Any}, base_d, "deps")::Dict{String, Any} + workspace_deps[name] = UUID(_uuid::String) + end + # Add deps from each workspace member project + ws = get(base_d, "workspace", nothing)::Union{Dict{String, Any}, Nothing} + if ws !== nothing + ws_projects = get(ws, "projects", nothing)::Union{Vector{String}, Nothing, String} + if ws_projects isa Vector + ws_root = dirname(base) + for ws_proj in ws_projects + ws_proj_dir = joinpath(ws_root, ws_proj) + ws_proj_file = Base.env_project_file(ws_proj_dir) + ws_proj_file isa String || continue + ws_d = parsed_toml(ws_proj_file) + for (name, _uuid) in get(Dict{String, Any}, ws_d, "deps")::Dict{String, Any} + workspace_deps[name] = UUID(_uuid::String) + end + end + end + end + end + return ExplicitEnv(envpath, project_deps, project_weakdeps, project_extras, - project_extensions, deps_expanded, weakdeps_expanded, extensions_expanded, + project_extensions, workspace_deps, + deps_expanded, weakdeps_expanded, extensions_expanded, names, lookup_strategy, #=prefs, local_prefs=#) end @@ -318,8 +350,8 @@ function show_progress(io::IO, p::MiniProgressBar; termwidth=nothing, carriagere end termwidth = @something termwidth (displaysize(io)::Tuple{Int,Int})[2] max_progress_width = max(0, min(termwidth - textwidth(p.header) - textwidth(progress_text) - 10 , p.width)) - n_filled = floor(Int, max_progress_width * perc / 100) - partial_filled = (max_progress_width * perc / 100) - n_filled + filled = max_progress_width * clamp(perc / 100, 0.0, 1.0) + (partial_filled, n_filled::Int64) = modf(filled) # get fractional / integer part n_left = max_progress_width - n_filled headers = split(p.header, ' ') to_print = sprint(; context=io) do io @@ -406,7 +438,7 @@ function excluded_circular_deps_explanation(io::IOContext{IO}, ext_to_parent::Di else line = " β””" * "─" ^j * " " end - hascolor = get(io, :color, false)::Bool + hascolor = get(io, :color, false)::Bool # XXX: this output does not go to `io` so this is bad to call here line = _color_string(line, :light_black, hascolor) * full_name(ext_to_parent, pkg) * "\n" cycle_str *= line end @@ -471,6 +503,76 @@ function collect_all_deps(direct_deps, dep, alldeps=Set{Base.PkgId}()) end +""" + precompilepkgs(pkgs; kwargs...) + +Precompile packages and their dependencies, with support for parallel compilation, +progress tracking, and various compilation configurations. + +`pkgs::Union{Vector{String}, Vector{PkgId}}`: Packages to precompile. When +empty (default), precompiles all project dependencies. When specified, +precompiles only the given packages and their dependencies (unless +`manifest=true`). + +!!! note + Errors will only throw when precompiling the top-level dependencies, given that + not all manifest dependencies may be loaded by the top-level dependencies on the given system. + This can be overridden to make errors in all dependencies throw by setting the kwarg `strict` to `true` + +# Keyword Arguments +- `internal_call::Bool`: Indicates this is an automatic precompilation call + from somewhere external (e.g. Pkg). Do not use this parameter. + +- `strict::Bool`: Controls error reporting scope. When `false` (default), only reports + errors for direct project dependencies. Only relevant when `manifest=true`. + +- `warn_loaded::Bool`: When `true` (default), checks for and warns about packages that are + precompiled but already loaded with a different version. Displays a warning that Julia + needs to be restarted to use the newly precompiled versions. + +- `timing::Bool`: When `true` (not default), displays timing information for + each package compilation, but only if compilation might have succeeded. + Disables fancy progress bar output (timing is shown in simple text mode). + +- `_from_loading::Bool`: Internal flag indicating the call originated from the + package loading system. When `true` (not default): returns early instead of + throwing when packages are not found; suppresses progress messages when not + in an interactive session; allows packages outside the current environment to + be added as serial precompilation jobs; skips LOADING_CACHE initialization; + and changes cachefile locking behavior. + +- `configs::Union{Config,Vector{Config}}`: Compilation configurations to use. Each Config + is a `Pair{Cmd, Base.CacheFlags}` specifying command flags and cache flags. When + multiple configs are provided, each package is precompiled for each configuration. + +- `io::IO`: Output stream for progress messages, warnings, and errors. Can be + redirected (e.g., to `devnull` when called from loading in non-interactive mode). + +- `fancyprint::Bool`: Controls output format. When `true`, displays an animated progress + bar with spinners. When `false`, instead enables `timing` mode. Automatically + disabled when `timing=true` or when called from loading in non-interactive mode. + +- `manifest::Bool`: Controls the scope of packages to precompile. When `false` (default), + precompiles only packages specified in `pkgs` and their dependencies. When `true`, + precompiles all packages in the manifest (workspace mode), typically used by Pkg for + workspace precompile requests. + +- `ignore_loaded::Bool`: Controls whether already-loaded packages affect cache + freshness checks. When `false` (not default), loaded package versions are considered when + determining if cache files are fresh. + +# Return +- `Vector{String}`: Paths to cache files for the requested packages. +- `Nothing`: precompilation should be skipped + +# Notes +- Packages in circular dependency cycles are skipped with a warning. +- Packages with `__precompile__(false)` are skipped if they are from loading to + avoid repeated work on every session. +- Parallel compilation is controlled by `JULIA_NUM_PRECOMPILE_TASKS` environment variable + (defaults to CPU_THREADS + 1, capped at 16, halved on Windows). +- Extensions are precompiled when all their triggers are available in the environment. +""" function precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}=String[]; internal_call::Bool=false, strict::Bool = false, @@ -505,6 +607,14 @@ function _visit_indirect_deps!(direct_deps::Dict{PkgId, Vector{PkgId}}, visited: return end +function _collect_reachable!(pkg_uuids::Set{UUID}, deps::Dict{UUID, Vector{UUID}}, uuid::UUID) + uuid in pkg_uuids && return + push!(pkg_uuids, uuid) + for dep_uuid in get(Vector{UUID}, deps, uuid) + _collect_reachable!(pkg_uuids, deps, dep_uuid) + end +end + function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, internal_call::Bool, strict::Bool, @@ -512,7 +622,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, timing::Bool, _from_loading::Bool, configs::Vector{Config}, - _io::IOContext{IO}, + io::IOContext{IO}, fancyprint::Bool, manifest::Bool, ignore_loaded::Bool) @@ -550,16 +660,20 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, # suppress precompilation progress messages when precompiling for loading packages, except during interactive sessions # or when specified by logging heuristics that explicitly require it # since the complicated IO implemented here can have somewhat disastrous consequences when happening in the background (e.g. #59599) - io = _io + logio = io logcalls = nothing - if _from_loading && !isinteractive() - io = IOContext{IO}(devnull) - fancyprint = false - logcalls = isinteractive() ? CoreLogging.Info : CoreLogging.Debug # sync with Base.compilecache + if _from_loading + if isinteractive() + logcalls = CoreLogging.Info # sync with Base.compilecache + else + logio = IOContext{IO}(devnull) + fancyprint = false + logcalls = CoreLogging.Debug # sync with Base.compilecache + end end nconfigs = length(configs) - hascolor = get(io, :color, false)::Bool + hascolor = get(logio, :color, false)::Bool color_string(cstr::String, col::Union{Int64, Symbol}) = _color_string(cstr, col, hascolor) stale_cache = Dict{StaleCacheKey, Bool}() @@ -590,21 +704,31 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, return name end + # Determine which packages to consider for precompilation by walking + # transitive dependencies from the appropriate roots. + # `manifest` controls the scope: workspace_deps (all members) vs project_deps (current project). + roots = manifest ? env.workspace_deps : env.project_deps + pkg_uuids = Set{UUID}() + for (_, uuid) in roots + _collect_reachable!(pkg_uuids, env.deps, uuid) + end + triggers = Dict{Base.PkgId,Vector{Base.PkgId}}() - for (dep, deps) in env.deps + for dep in pkg_uuids + haskey(env.deps, dep) || continue pkg = Base.PkgId(dep, env.names[dep]) Base.in_sysimage(pkg) && continue - deps = [Base.PkgId(x, env.names[x]) for x in deps] + deps = [Base.PkgId(x, env.names[x]) for x in env.deps[dep]] direct_deps[pkg] = filter!(!Base.in_sysimage, deps) - for (ext_name, trigger_uuids) in env.extensions[dep] + for (ext_name, trigger_uuids) in get(Dict{String, Vector{UUID}}, env.extensions, dep) ext_uuid = Base.uuid5(pkg.uuid, ext_name) ext = Base.PkgId(ext_uuid, ext_name) triggers[ext] = Base.PkgId[pkg] # depends on parent package all_triggers_available = true for trigger_uuid in trigger_uuids - trigger_name = env.names[trigger_uuid] - if trigger_uuid in keys(env.deps) - push!(triggers[ext], Base.PkgId(trigger_uuid, trigger_name)) + trigger_name = Base.PkgId(trigger_uuid, env.names[trigger_uuid]) + if trigger_uuid in pkg_uuids || Base.in_sysimage(trigger_name) + push!(triggers[ext], trigger_name) else all_triggers_available = false break @@ -634,6 +758,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, for ext_a in keys(ext_to_parent) for ext_b in keys(ext_to_parent) if triggers[ext_a] βŠ‹ triggers[ext_b] + push!(triggers[ext_a], ext_b) push!(direct_deps[ext_a], ext_b) end end @@ -653,21 +778,31 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, return indirect_deps end - # this loop must be run after the full direct_deps map has been populated - indirect_deps = expand_indirect_dependencies(direct_deps) - for ext in keys(ext_to_parent) - ext_loadable_in_pkg = Dict{Base.PkgId,Bool}() - for pkg in keys(direct_deps) - is_trigger = in(pkg, direct_deps[ext]) - is_extension = in(pkg, keys(ext_to_parent)) - has_triggers = issubset(direct_deps[ext], indirect_deps[pkg]) - ext_loadable_in_pkg[pkg] = !is_extension && has_triggers && !is_trigger - end - for (pkg, ext_loadable) in ext_loadable_in_pkg - if ext_loadable && !any((dep)->ext_loadable_in_pkg[dep], direct_deps[pkg]) - # add an edge if the extension is loadable by pkg, and was not loadable in any - # of the pkg's dependencies - push!(direct_deps[pkg], ext) + # This loop must be run after the full direct_deps map has been populated. + # Iterate to a fixed point because adding an extension edge (e.g. ExtA β†’ TopPkg) + # may cause another extension (e.g. ExtAB, which depends on ExtA) to become + # loadable in TopPkg on the next iteration. + changed = true + while changed + changed = false + indirect_deps = expand_indirect_dependencies(direct_deps) + for ext in keys(ext_to_parent) + ext_loadable_in_pkg = Dict{Base.PkgId,Bool}() + for pkg in keys(direct_deps) + is_trigger = in(pkg, direct_deps[ext]) + is_extension = in(pkg, keys(ext_to_parent)) + has_triggers = issubset(direct_deps[ext], indirect_deps[pkg]) + ext_loadable_in_pkg[pkg] = !is_extension && has_triggers && !is_trigger + end + for (pkg, ext_loadable) in ext_loadable_in_pkg + if ext_loadable && !any((dep)->ext_loadable_in_pkg[dep], direct_deps[pkg]) + if ext βˆ‰ direct_deps[pkg] + # add an edge if the extension is loadable by pkg, and was not loadable in any + # of the pkg's dependencies + push!(direct_deps[pkg], ext) + changed = true + end + end end end end @@ -738,17 +873,10 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, @warn excluded_circular_deps_explanation(io, ext_to_parent, circular_deps, cycles) end - # If you have a workspace and want to precompile all projects in it, look through all packages in the manifest - # instead of collecting from a project i.e. not filter out packages that are in the current project. - # i.e. Pkg sets manifest to true for workspace precompile requests - # TODO: rename `manifest`? - if !manifest - if isempty(pkg_names) - pkg_names = [pkg.name for pkg in project_deps] - end + # Filter to specific requested packages if the caller asked for a subset + if !isempty(pkg_names) keep = Set{Base.PkgId}() - for dep in direct_deps - dep_pkgid = first(dep) + for dep_pkgid in keys(direct_deps) if dep_pkgid.name in pkg_names push!(keep, dep_pkgid) collect_all_deps(direct_deps, dep_pkgid, keep) @@ -769,15 +897,16 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, end end filter!(d->in(first(d), keep), direct_deps) - if isempty(direct_deps) - if _from_loading - # if called from loading precompilation it may be a package from another environment stack so - # don't error and allow serial precompilation to try - # TODO: actually handle packages from other envs in the stack - return - else - return - end + end + + if isempty(direct_deps) + if _from_loading + # if called from loading precompilation it may be a package from another environment stack so + # don't error and allow serial precompilation to try + # TODO: actually handle packages from other envs in the stack + return + else + return end end @@ -808,6 +937,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, n_done = Ref(0) n_already_precomp = Ref(0) n_loaded = Ref(0) + loaded_pkgs = Base.PkgId[] interrupted = Ref(false) function handle_interrupt(err, in_printloop::Bool) @@ -837,9 +967,10 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, pkg_liveprinted = Ref{Union{Nothing, PkgId}}(nothing) function monitor_std(pkg_config, pipe; single_requested_pkg=false) - pkg, config = pkg_config + local pkg, config = pkg_config try - liveprinting = false + local liveprinting = false + local thistaskwaiting = false while !eof(pipe) local str = readline(pipe, keep=true) if single_requested_pkg && (liveprinting || !isempty(str)) @@ -852,15 +983,18 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, end end write(get!(IOBuffer, std_outputs, pkg_config), str) - if !in(pkg_config, taskwaiting) && occursin("waiting for IO to finish", str) - !fancyprint && @lock print_lock begin - println(io, pkg.name, color_string(" Waiting for background task / IO / timer.", Base.warn_color())) + if thistaskwaiting + if occursin("Waiting for background task / IO / timer", str) + thistaskwaiting = true + !liveprinting && !fancyprint && @lock print_lock begin + println(io, full_name(ext_to_parent, pkg), color_string(str, Base.warn_color())) + end + push!(taskwaiting, pkg_config) end - push!(taskwaiting, pkg_config) - end - if !fancyprint && in(pkg_config, taskwaiting) - @lock print_lock begin - print(io, str) + else + # XXX: don't just re-enable IO for random packages without printing the context for them first + !liveprinting && !fancyprint && @lock print_lock begin + print(io, ansi_cleartoendofline, str) end end end @@ -876,10 +1010,10 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, (isempty(pkg_queue) || interrupted_or_done[]) && return @lock print_lock begin if target[] !== nothing - printpkgstyle(io, :Precompiling, target[]) + printpkgstyle(logio, :Precompiling, target[]) end if fancyprint - print(io, ansi_disablecursor) + print(logio, ansi_disablecursor) end end t = Timer(0; interval=1/10) @@ -893,7 +1027,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, n_print_rows = 0 while !printloop_should_exit[] @lock print_lock begin - term_size = displaysize(io)::Tuple{Int, Int} + term_size = displaysize(logio)::Tuple{Int, Int} num_deps_show = max(term_size[1] - 3, 2) # show at least 2 deps pkg_queue_show = if !interrupted_or_done[] && length(pkg_queue) > num_deps_show last(pkg_queue, num_deps_show) @@ -904,13 +1038,16 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, if i > 1 print(iostr, ansi_cleartoend) end - bar.current = n_done[] - n_already_precomp[] - bar.max = n_total - n_already_precomp[] + # max(0,...) guards against a race where the print loop runs after + # n_already_precomp is incremented but before n_done is incremented, + # which would otherwise produce a negative value and crash repeat(). + bar.current = max(0, n_done[] - n_already_precomp[]) + bar.max = max(0, n_total - n_already_precomp[]) # when sizing to the terminal width subtract a little to give some tolerance to resizing the # window between print cycles termwidth = (displaysize(io)::Tuple{Int,Int})[2] - 4 if !final_loop - s = sprint(io -> show_progress(io, bar; termwidth, carriagereturn=false); context=io) + s = sprint(io -> show_progress(io, bar; termwidth, carriagereturn=false); context=logio) print(iostr, Base._truncate_at_width_or_chars(true, s, termwidth), "\n") end for pkg_config in pkg_queue_show @@ -951,11 +1088,11 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, end last_length = length(pkg_queue_show) n_print_rows = count("\n", str_) - print(io, str_) + print(logio, str_) printloop_should_exit[] = interrupted_or_done[] && final_loop final_loop = interrupted_or_done[] # ensures one more loop to tidy last task after finish i += 1 - printloop_should_exit[] || print(io, ansi_moveup(n_print_rows), ansi_movecol1) + printloop_should_exit[] || print(logio, ansi_moveup(n_print_rows), ansi_movecol1) end wait(t) end @@ -965,7 +1102,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, # Base.display_error(ErrorException(""), Base.catch_backtrace()) handle_interrupt(err, true) || rethrow() finally - fancyprint && print(io, ansi_enablecursor) + fancyprint && print(logio, ansi_enablecursor) end end @@ -992,8 +1129,12 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, notify(was_processed[pkg_config]) continue end - # Heuristic for when precompilation is disabled - if occursin(r"\b__precompile__\(\s*false\s*\)", read(sourcepath, String)) + # Heuristic for when precompilation is disabled, which must not over-estimate however for any dependent + # since it will also block precompilation of all dependents + if _from_loading && single_requested_pkg && occursin(r"\b__precompile__\(\s*false\s*\)", read(sourcepath, String)) + @lock print_lock begin + Base.@logmsg logcalls "Disabled precompiling $(repr("text/plain", pkg)) since the text `__precompile__(false)` was found in file." + end notify(was_processed[pkg_config]) continue end @@ -1015,8 +1156,8 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, end if !circular && is_stale Base.acquire(parallel_limiter) - is_project_dep = pkg in project_deps is_serial_dep = pkg in serial_deps + is_project_dep = pkg in project_deps # std monitoring std_pipe = Base.link_pipe!(Pipe(); reader_supports_async=true, writer_supports_async=true) @@ -1026,7 +1167,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, name = describe_pkg(pkg, is_project_dep, is_serial_dep, flags, cacheflags) @lock print_lock begin if !fancyprint && isempty(pkg_queue) - printpkgstyle(io, :Precompiling, something(target[], "packages...")) + printpkgstyle(logio, :Precompiling, something(target[], "packages...")) end end push!(pkg_queue, pkg_config) @@ -1035,9 +1176,14 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, if interrupted_or_done[] return end - # for extensions, any extension in our direct dependencies is one we have a right to load - # for packages, we may load any extension (all possible triggers are accounted for above) - loadable_exts = haskey(ext_to_parent, pkg) ? filter((dep)->haskey(ext_to_parent, dep), direct_deps[pkg]) : nothing + # for extensions, any extension that can trigger it needs to be accounted for here (even stdlibs, which are excluded from direct_deps) + loadable_exts = haskey(ext_to_parent, pkg) ? filter((dep)->haskey(ext_to_parent, dep), triggers[pkg]) : nothing + if !isempty(deps) + # if deps is empty, either it doesn't have any (so compiled-modules is + # irrelevant) or we couldn't compute them (so we actually should attempt + # serial compile, as the dependencies are not in the parallel list) + flags = `$flags --compiled-modules=strict` + end if _from_loading && pkg in requested_pkgids # loading already took the cachefile_lock and printed logmsg for its explicit requests t = @elapsed ret = begin @@ -1047,7 +1193,8 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, else # allows processes to wait if another process is precompiling a given package to # a functionally identical package cache (except for preferences, which may differ) - t = @elapsed ret = precompile_pkgs_maybe_cachefile_lock(io, print_lock, fancyprint, pkg_config, pkgspidlocked, hascolor, parallel_limiter) do + fullname = full_name(ext_to_parent, pkg) + t = @elapsed ret = precompile_pkgs_maybe_cachefile_lock(io, print_lock, fancyprint, pkg_config, pkgspidlocked, hascolor, parallel_limiter, fullname) do # refresh and double-check the search now that we have global lock if interrupted_or_done[] return ErrorException("canceled") @@ -1059,8 +1206,8 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, push!(freshpaths, freshpath) return nothing # returning nothing indicates another process did the recompile end - logcalls === nothing || @lock print_lock begin - Base.@logmsg logcalls "Precompiling $(repr("text/plain", pkg))" + logcalls === CoreLogging.Debug && @lock print_lock begin + @debug "Precompiling $(repr("text/plain", pkg))" end Base.compilecache(pkg, sourcepath, std_pipe, std_pipe, !ignore_loaded; flags, cacheflags, loadable_exts) @@ -1069,11 +1216,11 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, if ret isa Exception push!(precomperr_deps, pkg_config) !fancyprint && @lock print_lock begin - println(io, _timing_string(t), color_string(" ? ", Base.warn_color()), name) + println(logio, _timing_string(t), color_string(" ? ", Base.warn_color()), name) end else !fancyprint && @lock print_lock begin - println(io, _timing_string(t), color_string(" βœ“ ", loaded ? Base.warn_color() : :green), name) + println(logio, _timing_string(t), color_string(" βœ“ ", loaded ? Base.warn_color() : :green), name) end if ret !== nothing was_recompiled[pkg_config] = true @@ -1082,18 +1229,23 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, build_id, _ = Base.parse_cache_buildid(cachefile) stale_cache_key = (pkg, build_id, sourcepath, cachefile, ignore_loaded, cacheflags)::StaleCacheKey stale_cache[stale_cache_key] = false + if loaded && Base.module_build_id(Base.loaded_modules[pkg]) != build_id + n_loaded[] += 1 + @lock print_lock push!(loaded_pkgs, pkg) + end + elseif loaded + # another process compiled this package; conservatively warn + n_loaded[] += 1 + @lock print_lock push!(loaded_pkgs, pkg) end end - loaded && (n_loaded[] += 1) catch err close(std_pipe.in) # close pipe to end the std output monitor wait(t_monitor) if err isa ErrorException || (err isa ArgumentError && startswith(err.msg, "Invalid header in cache file")) - errmsg = String(take!(get(IOBuffer, std_outputs, pkg_config))) - delete!(std_outputs, pkg_config) # so it's not shown as warnings, given error report - failed_deps[pkg_config] = (strict || is_project_dep) ? string(sprint(showerror, err), "\n", strip(errmsg)) : "" + failed_deps[pkg_config] = sprint(showerror, err) !fancyprint && @lock print_lock begin - println(io, " "^12, color_string(" βœ— ", Base.error_color()), name) + println(logio, " "^12, color_string(" βœ— ", Base.error_color()), name) end else rethrow() @@ -1104,7 +1256,16 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, Base.release(parallel_limiter) end else - is_stale || (n_already_precomp[] += 1) + if !is_stale + n_already_precomp[] += 1 + if loaded + fresh_build_id, _ = Base.parse_cache_buildid(freshpath) + if Base.module_build_id(Base.loaded_modules[pkg]) != fresh_build_id + n_loaded[] += 1 + @lock print_lock push!(loaded_pkgs, pkg) + end + end + end end n_done[] += 1 notify(was_processed[pkg_config]) @@ -1141,11 +1302,27 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, quick_exit = any(t -> !istaskdone(t) || istaskfailed(t), tasks) || interrupted[] # all should have finished (to avoid memory corruption) 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(context=io) do iostr - if !quick_exit + # Determine if any of failures were a requested package + requested_errs = false + for ((dep, config), err) in failed_deps + if dep in requested_pkgids + requested_errs = true + break + end + end + # if every requested package succeeded, filter away output from failed packages + # since it didn't contribute to the overall success and can be regenerated if that package is later required + if !strict && !requested_errs + for (pkg_config, err) in failed_deps + delete!(std_outputs, pkg_config) + end + empty!(failed_deps) + end + if ndeps > 0 || !isempty(failed_deps) + if !quick_exit + logstr = sprint(context=logio) do iostr if fancyprint # replace the progress bar - what = isempty(requested_pkgids) ? "packages finished." : "$(join((p.name for p in requested_pkgids), ", ", " and ")) finished." + what = isempty(requested_pkgids) ? "packages finished." : "$(join((full_name(ext_to_parent, p) for p in requested_pkgids), ", ", " and ")) finished." printpkgstyle(iostr, :Precompiling, what) end plural = length(configs) > 1 ? "dependency configurations" : ndeps == 1 ? "dependency" : "dependencies" @@ -1159,14 +1336,44 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, local plural1 = length(configs) > 1 ? "dependency configurations" : n_loaded[] == 1 ? "dependency" : "dependencies" local plural2 = n_loaded[] == 1 ? "a different version is" : "different versions are" local plural3 = n_loaded[] == 1 ? "" : "s" - local plural4 = n_loaded[] == 1 ? "this package" : "these packages" + local loaded_names = join(sort!([full_name(ext_to_parent, p) for p in loaded_pkgs]), ", ", " and ") + # compute how many precompiled packages transitively depend on the loaded packages + local n_affected = 0 + local loaded_set = Set{Base.PkgId}(loaded_pkgs) + let reverse_deps = Dict{Base.PkgId, Vector{Base.PkgId}}() + for (p, deps) in direct_deps + for d in deps + push!(get!(Vector{Base.PkgId}, reverse_deps, d), p) + end + end + affected = Set{Base.PkgId}() + frontier = Base.PkgId[p for p in loaded_set] + while !isempty(frontier) + p = pop!(frontier) + for rdep in get(reverse_deps, p, Base.PkgId[]) + if rdep βˆ‰ affected && rdep βˆ‰ loaded_set + push!(affected, rdep) + push!(frontier, rdep) + end + end + end + n_affected = length(affected) + end print(iostr, "\n ", color_string(string(n_loaded[]), Base.warn_color()), " $(plural1) precompiled but ", color_string("$(plural2) currently loaded", Base.warn_color()), - ". Restart julia to access the new version$(plural3). \ - Otherwise, loading dependents of $(plural4) may trigger further precompilation to work with the unexpected version$(plural3)." + " (", loaded_names, ")", + ". Restart julia to access the new version$(plural3)." ) + if n_affected > 0 + local affected_plural = length(configs) > 1 ? "dependency configurations" : n_affected == 1 ? "dependent" : "dependents" + print(iostr, + " Otherwise, $(n_affected) $(affected_plural) of ", + n_loaded[] == 1 ? "this package" : "these packages", + " may trigger further precompilation to work with the unexpected version$(plural3)." + ) + end end if !isempty(precomperr_deps) pluralpc = length(configs) > 1 ? "dependency configurations" : precomperr_deps == 1 ? "dependency" : "dependencies" @@ -1176,10 +1383,17 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, ) end end + @lock print_lock begin + println(logio, logstr) + end + end + end + if !isempty(std_outputs) + str = sprint(context=io) do iostr # show any stderr output, even if Pkg.precompile has been interrupted (quick_exit=true), given user may be - # interrupting a hanging precompile job with stderr output. julia#48371 + # interrupting a hanging precompile job with stderr output. let std_outputs = Tuple{PkgConfig,SubString{String}}[(pkg_config, strip(String(take!(io)))) for (pkg_config,io) in std_outputs] - filter!(kv -> !isempty(last(kv)), std_outputs) + filter!(!isempty∘last, std_outputs) if !isempty(std_outputs) local plural1 = length(std_outputs) == 1 ? "y" : "ies" local plural2 = length(std_outputs) == 1 ? "" : "s" @@ -1197,49 +1411,32 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}, end end end - @lock print_lock begin + isempty(str) || @lock print_lock begin println(io, str) end - if interrupted[] - # done cleanup, now ensure caller aborts too - throw(InterruptException()) - end - quick_exit && return Vector{String}[] + end + # Done cleanup and sub-process output, now ensure caller aborts too with the right error + if interrupted[] + throw(InterruptException()) + end + # Fail noisily now with failed_deps if any. + # Include all messages from compilecache since any might be relevant in the failure. + if !isempty(failed_deps) err_str = IOBuffer() - n_direct_errs = 0 - for (pkg_config, err) in failed_deps - dep, config = pkg_config - if strict || (dep in project_deps) - print(err_str, "\n", dep.name, " ") - for cfg in config[1] - print(err_str, cfg, " ") - end - print(err_str, "\n\n", err) - n_direct_errs > 0 && write(err_str, "\n") - n_direct_errs += 1 - end + for ((dep, config), err) in failed_deps + write(err_str, "\n") + print(err_str, "\n", full_name(ext_to_parent, dep), " ") + join(err_str, config[1], " ") + print(err_str, "\n", err) end - if position(err_str) > 0 - skip(err_str, -1) - truncate(err_str, position(err_str)) - pluralde = n_direct_errs == 1 ? "y" : "ies" - direct = strict ? "" : "direct " - err_msg = "The following $n_direct_errs $(direct)dependenc$(pluralde) failed to precompile:\n$(String(take!(err_str)))" - if internal_call # aka. auto-precompilation - if isinteractive() - plural1 = length(failed_deps) == 1 ? "y" : "ies" - println(io, " ", color_string("$(length(failed_deps))", Base.error_color()), " dependenc$(plural1) errored.") - println(io, " For a report of the errors see `julia> err`. To retry use `pkg> precompile`") - setglobal!(Base.MainInclude, :err, PkgPrecompileError(err_msg)) - else - # auto-precompilation shouldn't throw but if the user can't easily access the - # error messages, just show them - print(io, "\n", err_msg) - end - else - println(io) - throw(PkgPrecompileError(err_msg)) - end + n_errs = length(failed_deps) + pluraled = n_errs == 1 ? "" : "s" + err_msg = "The following $n_errs package$(pluraled) failed to precompile:$(String(take!(err_str)))\n" + if internal_call + # Pkg does not implement correct error handling, so this sometimes handles them instead + print(io, err_msg) + else + throw(PkgPrecompileError(err_msg)) end end return collect(String, Iterators.flatten((v for (pkgid, v) in cachepath_cache if pkgid in requested_pkgids))) @@ -1263,7 +1460,7 @@ end # - `nothing`: cache already existed # - `Tuple{String, Union{Nothing, String}}`: this process just compiled # - `Exception`: compilation failed -function precompile_pkgs_maybe_cachefile_lock(f, io::IO, print_lock::ReentrantLock, fancyprint::Bool, pkg_config, pkgspidlocked, hascolor, parallel_limiter::Base.Semaphore) +function precompile_pkgs_maybe_cachefile_lock(f, io::IO, print_lock::ReentrantLock, fancyprint::Bool, pkg_config, pkgspidlocked, hascolor, parallel_limiter::Base.Semaphore, fullname) if !(isdefined(Base, :mkpidlock_hook) && isdefined(Base, :trymkpidlock_hook) && Base.isdefined(Base, :parse_pidfile_hook)) return f() end @@ -1284,7 +1481,7 @@ function precompile_pkgs_maybe_cachefile_lock(f, io::IO, print_lock::ReentrantLo "another machine (hostname: $hostname, pid: $pid, pidfile: $pidfile)" end !fancyprint && @lock print_lock begin - println(io, " ", pkg.name, _color_string(" Being precompiled by $(pkgspidlocked[pkg_config])", Base.info_color(), hascolor)) + println(io, " ", fullname, _color_string(" Being precompiled by $(pkgspidlocked[pkg_config])", Base.info_color(), hascolor)) end Base.release(parallel_limiter) # release so other work can be done while waiting try diff --git a/base/process.jl b/base/process.jl index 141a7615b599b..933e3bcd8f17e 100644 --- a/base/process.jl +++ b/base/process.jl @@ -556,7 +556,7 @@ const SIGPIPE = 13 # !windows const SIGTERM = 15 function test_success(proc::Process) - @assert process_exited(proc) + @assert process_exited(proc) "process did not exit successfully" if proc.exitcode < 0 #TODO: this codepath is not currently tested throw(_UVError("could not start process " * repr(proc.cmd), proc.exitcode)) @@ -634,7 +634,7 @@ permissions). function kill(p::Process, signum::Integer=SIGTERM) iolock_begin() if process_running(p) - @assert p.handle != C_NULL + @assert p.handle != C_NULL "invalid handle" err = ccall(:uv_process_kill, Int32, (Ptr{Cvoid}, Int32), p.handle, signum) if err != 0 && err != UV_ESRCH throw(_UVError("kill", err)) diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 39fee20a31227..1e0257e10ac12 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -267,7 +267,7 @@ to_index(i::SCartesianIndex2) = i struct SCartesianIndices2{K,R<:AbstractUnitRange{Int}} <: AbstractMatrix{SCartesianIndex2{K}} indices2::R end -SCartesianIndices2{K}(indices2::AbstractUnitRange{Int}) where {K} = (@assert K::Int > 1; SCartesianIndices2{K,typeof(indices2)}(indices2)) +SCartesianIndices2{K}(indices2::AbstractUnitRange{Int}) where {K} = (@assert K::Int > 1 "invalid index"; SCartesianIndices2{K,typeof(indices2)}(indices2)) eachindex(::IndexSCartesian2{K}, A::ReshapedReinterpretArray) where {K} = SCartesianIndices2{K}(eachindex(IndexLinear(), parent(A))) @inline function eachindex(style::IndexSCartesian2{K}, A::AbstractArray, B::AbstractArray...) where {K} diff --git a/base/ryu/exp.jl b/base/ryu/exp.jl index 4f749668867e2..cd225cdd7b8e8 100644 --- a/base/ryu/exp.jl +++ b/base/ryu/exp.jl @@ -1,7 +1,7 @@ function writeexp(buf, pos, v::T, precision=-1, plus=false, space=false, hash=false, expchar=UInt8('e'), decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat} - @assert 0 < pos <= length(buf) + @assert 0 < pos <= length(buf) "invalid pos" startpos = pos x = Float64(v) pos = append_sign(x, plus, space, buf, pos) diff --git a/base/ryu/fixed.jl b/base/ryu/fixed.jl index 96777059bc284..a5e178c4c4d1f 100644 --- a/base/ryu/fixed.jl +++ b/base/ryu/fixed.jl @@ -1,7 +1,7 @@ function writefixed(buf, pos, v::T, precision=-1, plus=false, space=false, hash=false, decchar=UInt8('.'), trimtrailingzeros=false) where {T <: Base.IEEEFloat} - @assert 0 < pos <= length(buf) + @assert 0 < pos <= length(buf) "invalid pos" startpos = pos x = Float64(v) pos = append_sign(x, plus, space, buf, pos) diff --git a/base/ryu/shortest.jl b/base/ryu/shortest.jl index 13d72f225f867..0add782757ecd 100644 --- a/base/ryu/shortest.jl +++ b/base/ryu/shortest.jl @@ -229,7 +229,7 @@ function writeshortest(buf::AbstractVector{UInt8}, pos, x::T, plus=false, space=false, hash=true, precision=-1, expchar=UInt8('e'), padexp=false, decchar=UInt8('.'), typed=false, compact=false) where {T} - @assert 0 < pos <= length(buf) + @assert 0 < pos <= length(buf) "invalid pos" # special cases if x == 0 if typed && x isa Float16 diff --git a/base/set.jl b/base/set.jl index 8b8f3d44603c8..aa97a55e78e58 100644 --- a/base/set.jl +++ b/base/set.jl @@ -881,8 +881,8 @@ askey(k, ::AbstractSet) = k function _replace!(new::Callable, res::Union{AbstractDict,AbstractSet}, A::Union{AbstractDict,AbstractSet}, count::Int) - @assert res isa AbstractDict && A isa AbstractDict || - res isa AbstractSet && A isa AbstractSet + @assert (res isa AbstractDict && A isa AbstractDict || + res isa AbstractSet && A isa AbstractSet) "type mismatch" count == 0 && return res c = 0 if res === A # cannot replace elements while iterating over A diff --git a/base/show.jl b/base/show.jl index 0701d4c50558b..83d16cc054e3f 100644 --- a/base/show.jl +++ b/base/show.jl @@ -3336,7 +3336,7 @@ function print_partition(io::IO, partition::Core.BindingPartition) print(io, "explicit `import` from ") print(io, partition_restriction(partition).globalref) else - @assert kind == PARTITION_KIND_GLOBAL + @assert kind == PARTITION_KIND_GLOBAL "unexpected partition kind" print(io, "global variable with type ") print(io, partition_restriction(partition)) end diff --git a/base/sort.jl b/base/sort.jl index f434aa3ed4e88..5c47335a37ef1 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -599,7 +599,7 @@ struct WithoutMissingVector{T, U} <: AbstractVector{T} end Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i::Integer) out = v.data[i] - @assert !(out isa Missing) + @assert !(out isa Missing) "encountered `missing` in WithoutMissingVector" out::eltype(v) end Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector, x, i::Integer) @@ -1251,13 +1251,13 @@ function move!(v, target, source) # This function never dominates runtimeβ€”only add `@inbounds` if you can demonstrate a # performance improvement. And if you do, also double check behavior when `target` # is out of bounds. - @assert length(target) == length(source) + @assert length(target) == length(source) "length mismatch" if length(target) == 1 || isdisjoint(target, source) for (i, j) in zip(target, source) v[i], v[j] = v[j], v[i] end else - @assert minimum(source) <= minimum(target) + @assert minimum(source) <= minimum(target) "range mismatch" reverse!(v, minimum(source), maximum(target)) reverse!(v, minimum(target), maximum(target)) end @@ -1328,7 +1328,7 @@ function _sort!(v::AbstractVector, a::BracketedSort, o::Ordering, kw) # Specifically, this means that expected_middle_ln == ln, so # ln <= ... + 2.0expected_middle_ln && return ... # will trigger. - @assert false + @assert false "this should never happen" # But if it does happen, the kernel reduces to 0, hi elseif lo_signpost_i <= lo diff --git a/base/stream.jl b/base/stream.jl index 7b227458ec552..d2c1b93c4d2f9 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -918,8 +918,8 @@ readbytes!(s::LibuvStream, a::Vector{UInt8}, nb = length(a)) = readbytes!(s, a, function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb::Int) iolock_begin() sbuf = s.buffer - @assert sbuf.seekable == false - @assert sbuf.maxsize >= nb + @assert sbuf.seekable == false "buffer should not be seekable" + @assert sbuf.maxsize >= nb "insufficient buffer size" function wait_locked(s, buf, nb) while bytesavailable(buf) < nb @@ -966,8 +966,8 @@ end function unsafe_read(s::LibuvStream, p::Ptr{UInt8}, nb::UInt) iolock_begin() sbuf = s.buffer - @assert sbuf.seekable == false - @assert sbuf.maxsize >= nb + @assert sbuf.seekable == false "buffer should not be seekable" + @assert sbuf.maxsize >= nb "insufficient buffer size" function wait_locked(s, buf, nb) while bytesavailable(buf) < nb @@ -1002,7 +1002,7 @@ end function read(this::LibuvStream, ::Type{UInt8}) iolock_begin() sbuf = this.buffer - @assert sbuf.seekable == false + @assert sbuf.seekable == false "buffer should not be seekable" while bytesavailable(sbuf) < 1 iolock_end() eof(this) && throw(EOFError()) @@ -1017,7 +1017,7 @@ function readavailable(this::LibuvStream) wait_readnb(this, 1) # unlike the other `read` family of functions, this one doesn't guarantee error reporting iolock_begin() buf = this.buffer - @assert buf.seekable == false + @assert buf.seekable == false "buffer should not be seekable" bytes = take!(buf) iolock_end() return bytes @@ -1026,7 +1026,7 @@ end function copyuntil(out::IO, x::LibuvStream, c::UInt8; keep::Bool=false) iolock_begin() buf = x.buffer - @assert buf.seekable == false + @assert buf.seekable == false "buffer should not be seekable" if !occursin(c, buf) # fast path checks first x.readerror === nothing || throw(x.readerror) if isopen(x) && x.status != StatusEOF @@ -1563,7 +1563,7 @@ function readavailable(this::BufferStream) bytes = lock(this.cond) do wait_readnb(this, 1) buf = this.buffer - @assert buf.seekable == false + @assert buf.seekable == false "buffer should not be seekable" take!(buf) end return bytes @@ -1579,8 +1579,8 @@ end function readbytes!(s::BufferStream, a::Vector{UInt8}, nb::Int) sbuf = s.buffer - @assert sbuf.seekable == false - @assert sbuf.maxsize >= nb + @assert sbuf.seekable == false "buffer should not be seekable" + @assert sbuf.maxsize >= nb "insufficient buffer size" function wait_locked(s, buf, nb) while bytesavailable(buf) < nb diff --git a/base/task.jl b/base/task.jl index 9841bd947fdee..2b83633c1df00 100644 --- a/base/task.jl +++ b/base/task.jl @@ -160,7 +160,7 @@ const task_state_failed = UInt8(2) elseif st === task_state_failed return :failed else - @assert false + @assert false "unexpected state" end elseif field === :backtrace # TODO: this field name should be deprecated in 2.0 @@ -1161,32 +1161,6 @@ function throwto(t::Task, @nospecialize exc) return try_yieldto(identity) end -function wait_forever() - while true - try - while true - wait() - end - catch e - local errs = stderr - # try to display the failure atomically - errio = IOContext(PipeBuffer(), errs::IO) - emphasize(errio, "Internal Task ") - display_error(errio, current_exceptions()) - write(errs, errio) - # victimize another random Task also - if Threads.threadid() == 1 && isa(e, InterruptException) && isempty(Workqueue) - backend = repl_backend_task() - backend isa Task && throwto(backend, e) - end - end - end -end - -const get_sched_task = OncePerThread{Task}() do - Task(wait_forever) -end - function ensure_rescheduled(othertask::Task) ct = current_task() W = workqueue_for(Threads.threadid()) @@ -1223,30 +1197,26 @@ end checktaskempty = Partr.multiq_check_empty +@noinline function poptask(W::StickyWorkqueue) + task = trypoptask(W) + if !(task isa Task) + task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any, Any), trypoptask, W, checktaskempty) + end + set_next_task(task) + nothing +end + function wait() ct = current_task() # [task] user_time -yield-or-done-> wait_time record_running_time!(ct) - # let GC run GC.safepoint() - # check for libuv events - process_events() - - # get the next task to run W = workqueue_for(Threads.threadid()) - task = trypoptask(W) - if task === nothing - # No tasks to run; switch to the scheduler task to run the - # thread sleep logic. - sched_task = get_sched_task() - if ct !== sched_task - istaskdone(sched_task) && (sched_task = @task wait()) - return yieldto(sched_task) - end - task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any, Any), trypoptask, W, checktaskempty) - end - set_next_task(task) - return try_yieldto(ensure_rescheduled) + poptask(W) + result = try_yieldto(ensure_rescheduled) + process_events() + # return when we come out of the queue + return result end if Sys.iswindows() diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index ab1ba40461af7..6b7210f690ef8 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -183,7 +183,7 @@ function threading_run(fun, static) # TODO: this should be the current pool (except interactive) if there # are ever more than two pools. _result = ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, _sym_to_tpid(:default)) - @assert _result == 1 + @assert _result == 1 "_result != 1" end tasks[i] = t schedule(t) @@ -444,7 +444,7 @@ function _spawn_set_thrpool(t::Task, tp::Symbol) tpid = _sym_to_tpid(:default) end _result = ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, tpid) - @assert _result == 1 + @assert _result == 1 "_result != 1" nothing end diff --git a/base/timing.jl b/base/timing.jl index 12e41491c80cf..57dc922424595 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -532,7 +532,7 @@ function _gen_allocation_measurer(ex, fname::Symbol) b1[] - b0[] end else - @assert fname === :allocations + @assert fname === :allocations "unexpected fname" return quote Experimental.@force_compile # Note this value is unused, but without it `allocated` and `allocations` diff --git a/base/toml/parser.jl b/base/toml/parser.jl index 9c83d09d2aa54..66c338985964b 100644 --- a/base/toml/parser.jl +++ b/base/toml/parser.jl @@ -290,7 +290,7 @@ end # used to show the interval where an error happened # Right now, it is only called with a == b function point_to_line(str::AbstractString, a::Int, b::Int, context) - @assert b >= a + @assert b >= a "invalid range" a = thisind(str, a) b = thisind(str, b) pos = something(findprev('\n', str, prevind(str, a)), 0) + 1 @@ -713,7 +713,7 @@ function parse_array(l::Parser{Dates})::Err{Vector} where Dates (Dates !== nothing && ((T === Dates.Date) || (T === Dates.Time) || (T === Dates.DateTime))) # do nothing, leave as Vector{Any} new = array - else @assert false end + else @assert false "unexpected type" end push!(l.static_arrays, new) return new end diff --git a/base/toml/printer.jl b/base/toml/printer.jl index aaef4768be347..2a39fc76c382c 100644 --- a/base/toml/printer.jl +++ b/base/toml/printer.jl @@ -128,12 +128,15 @@ function printvalue(f::Function, io::IO, value::Integer, sorted::Bool) end function printvalue(f::Function, io::IO, value::AbstractFloat, sorted::Bool) + # The early conversion here avoids invalidations from isnan/isinf + value = Float64(value) + if isnan(value) Base.print(io, "nan") elseif isinf(value) Base.print(io, value > 0 ? "+inf" : "-inf") else - Base.print(io, Float64(value)) # TOML specifies IEEE 754 binary64 for float + Base.print(io, value) # TOML specifies IEEE 754 binary64 for float end end diff --git a/base/views.jl b/base/views.jl index fca7e7ced9027..49b715126e5f9 100644 --- a/base/views.jl +++ b/base/views.jl @@ -26,7 +26,7 @@ function replace_ref_begin_end_!(__module__::Module, ex, withex, in_quote_contex return ex end function handle_refexpr!(__module__::Module, ref_ex::Expr, main_ex::Expr, withex, in_quote_context, escs::Int) - @assert !in_quote_context + @assert !in_quote_context "handle_refexpr! should not be called in quote context" local used_withex ref_ex.args[1], used_withex = replace_ref_begin_end_!(__module__, ref_ex.args[1], withex, in_quote_context, escs) S = gensym(:S) # temp var to cache ex.args[1] if needed. if S is a global or expression, then it has side effects to use diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 7fd46e58870f8..0b73f5a43b9d0 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -83,7 +83,6 @@ end #entrypoint(join, (Base.GenericIOBuffer{Memory{UInt8}}, Array{String, 1}, Char)) entrypoint(Base.task_done_hook, (Task,)) entrypoint(Base.wait, ()) -entrypoint(Base.wait_forever, ()) entrypoint(Base.trypoptask, (Base.StickyWorkqueue,)) entrypoint(Base.checktaskempty, ()) diff --git a/contrib/juliac/juliac-trim-base.jl b/contrib/juliac/juliac-trim-base.jl index fce275551751e..c7b5e5156611b 100644 --- a/contrib/juliac/juliac-trim-base.jl +++ b/contrib/juliac/juliac-trim-base.jl @@ -12,7 +12,6 @@ end depwarn(msg, funcsym; force::Bool=false) = nothing _assert_tostring(msg) = "" reinit_stdio() = nothing - wait_forever() = while true; wait(); end JuliaSyntax.enable_in_core!() = nothing init_active_project() = ACTIVE_PROJECT[] = nothing set_active_project(projfile::Union{AbstractString,Nothing}) = ACTIVE_PROJECT[] = projfile @@ -80,10 +79,35 @@ end end # these functions are not `--trim`-compatible if it resolves to a Varargs{...} specialization # and since it only has 1-argument methods this happens too often by default (just 2-3 args) + setfield!(typeof(throw_boundserror).name, :max_args, Int32(5), :monotonic) setfield!(typeof(throw_eachindex_mismatch_indices).name, :max_args, Int32(5), :monotonic) + setfield!(typeof(_throw_boundserror_indices).name, :max_args, Int32(5), :monotonic) setfield!(typeof(print).name, :max_args, Int32(10), :monotonic) setfield!(typeof(println).name, :max_args, Int32(10), :monotonic) setfield!(typeof(print_to_string).name, :max_args, Int32(10), :monotonic) + + # not `--trim`-compatible if these resolve to a Varargs{...} specialization, primarily + # due to `Core._apply_iterate` having no dispatch-resolved form (#57830) + setfield!(typeof(_cat).name, :max_args, Int32(10), :monotonic) + setfield!(typeof(__cat).name, :max_args, Int32(10), :monotonic) + setfield!(typeof(__cat_offset!).name, :max_args, Int32(10), :monotonic) + setfield!(typeof(cat_size_shape).name, :max_args, Int32(10), :monotonic) + setfield!(typeof(_cat_size_shape).name, :max_args, Int32(10), :monotonic) + + # vcat / hcat / hvcat / hvncat / cat all use this + unwrap_val(dims) = dims + unwrap_val(::Val{dims}) where dims = dims + function _cat_t(dims, ::Type{T}, X::Vararg{AbstractArray,N}) where {T,N} + @inline + Ndims = maximum(map(ndims, X)) + catdims = ntuple(in(unwrap_val(dims)), Val(Ndims)) + shape = cat_size_shape(catdims, X...) + A = cat_similar(X[1], T, shape) + if count(!iszero, catdims)::Int > 1 + fill!(A, zero(T)) + end + return __cat(A, shape, catdims, X...) + end end @eval Base.Sys begin __init_build() = nothing # VersionNumber parsing is not supported yet diff --git a/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/md5 b/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/md5 new file mode 100644 index 0000000000000..5e12286eb5962 --- /dev/null +++ b/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/md5 @@ -0,0 +1 @@ +34a93d1aec1126e8d36306a77f476173 diff --git a/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/sha512 b/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/sha512 new file mode 100644 index 0000000000000..017985c54873e --- /dev/null +++ b/deps/checksums/SparseArrays-5307f25727e4ea6506a740a2d99566f3e65cf105.tar.gz/sha512 @@ -0,0 +1 @@ +6aaaef5d9ca68bd85aaca277e9e1a2e24616d08cf6a3812601b073973694e9b0bc82cdde1eb1901632db0cd9c5b4310e05b4815f3503b7aa51ab7b4abbfbf234 diff --git a/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/md5 b/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/md5 deleted file mode 100644 index a7ac76aa09e8a..0000000000000 --- a/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -44270a346f9ddd509cea805b9bc81156 diff --git a/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/sha512 b/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/sha512 deleted file mode 100644 index fac4f9ef0e691..0000000000000 --- a/deps/checksums/SparseArrays-6b7e9ef30624f25d97bce047ad781a25bebe5551.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -24997365906fb555362223b9ec49dfa1990d09a83685e4b149e34687377ec11616d97b38de99b9d750fd99ee4f62b3d6462790f0fd26c1dfd26a1ca341ef5658 diff --git a/deps/jlutilities/juliac/Manifest.toml b/deps/jlutilities/juliac/Manifest.toml new file mode 100644 index 0000000000000..466eecab79ed1 --- /dev/null +++ b/deps/jlutilities/juliac/Manifest.toml @@ -0,0 +1,391 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.14.0-DEV" +manifest_format = "2.1" +project_hash = "d3f4758b27fc8d19cd1b7603061a3b69d4285c69" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + + [deps.ArgTools.syntax] + julia_version = "1.3.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + + [deps.Artifacts.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + + [deps.Base64.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.3.0+1" + + [deps.CompilerSupportLibraries_jll.syntax] + julia_version = "1.6.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + + [deps.Dates.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.7.0" + + [deps.Downloads.syntax] + julia_version = "1.10.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + + [deps.FileWatching.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Glob]] +git-tree-sha1 = "83cb0092e2792b9e3a865b6655e88f5b862607e2" +registries = "General" +uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" +version = "1.4.0" + + [deps.Glob.syntax] + julia_version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +registries = "General" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.1" + + [deps.JLLWrappers.syntax] + julia_version = "1.0.0" + +[[deps.JuliaC]] +deps = ["LazyArtifacts", "Libdl", "Mmap", "ObjectFile", "PackageCompiler", "Patchelf_jll", "Pkg", "RelocatableFolders", "StructIO"] +git-tree-sha1 = "d2d87d6d9f7a12c695bc23db869cdd9d4bcbccde" +repo-rev = "main" +repo-url = "https://github.com/JuliaLang/JuliaC.jl.git" +uuid = "acedd4c2-ced6-4a15-accc-2607eb759ba2" +version = "0.3.1" + + [deps.JuliaC.syntax] + julia_version = "1.10.0" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.13.0" + + [deps.JuliaSyntaxHighlighting.syntax] + julia_version = "1.12.0" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +version = "1.11.0" + + [deps.LazyArtifacts.syntax] + julia_version = "1.11.0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "1.0.0" + + [deps.LibCURL.syntax] + julia_version = "1.3.0" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "Zstd_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.19.0+0" + + [deps.LibCURL_jll.syntax] + julia_version = "1.11.0" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + + [deps.LibGit2.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.2+0" + + [deps.LibGit2_jll.syntax] + julia_version = "1.9.0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl", "OpenSSL_jll", "Zlib_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" + + [deps.LibSSH2_jll.syntax] + julia_version = "1.8.0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + + [deps.Libdl.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + + [deps.Logging.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + + [deps.Markdown.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + + [deps.Mmap.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2026.3.19" + + [deps.MozillaCACerts_jll.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + + [deps.NetworkOptions.syntax] + julia_version = "1.12.0" + +[[deps.ObjectFile]] +deps = ["Reexport", "StructIO"] +git-tree-sha1 = "22faba70c22d2f03e60fbc61da99c4ebfc3eb9ba" +registries = "General" +uuid = "d8793406-e978-5875-9003-1fc021f44a92" +version = "0.5.0" + + [deps.ObjectFile.syntax] + julia_version = "1.6.0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.6+0" + + [deps.OpenSSL_jll.syntax] + julia_version = "1.6.0" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.47.0+0" + + [deps.PCRE2_jll.syntax] + julia_version = "1.6.0" + +[[deps.PackageCompiler]] +deps = ["Artifacts", "Glob", "LazyArtifacts", "Libdl", "Pkg", "Printf", "RelocatableFolders", "TOML", "UUIDs", "p7zip_jll"] +git-tree-sha1 = "7b2dbae3d0eda41dad445b5f36f40588c208a942" +registries = "General" +uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" +version = "2.2.5" + + [deps.PackageCompiler.syntax] + julia_version = "1.10.0" + +[[deps.Patchelf_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ad1cab3e8273c912f722f40779e20f3e29d58f1f" +registries = "General" +uuid = "f2cf89d6-2bfd-5c44-bd2c-068eea195c0c" +version = "0.18.0+0" + + [deps.Patchelf_jll.syntax] + julia_version = "1.6.0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "Zstd_jll", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.14.0" + + [deps.Pkg.extensions] + REPLExt = "REPL" + + [deps.Pkg.syntax] + julia_version = "1.12.0" + + [deps.Pkg.weakdeps] + REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4" +registries = "General" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.2" + + [deps.Preferences.syntax] + julia_version = "1.0.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + + [deps.Printf.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + + [deps.Random.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +registries = "General" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + + [deps.Reexport.syntax] + julia_version = "1.0.0" + +[[deps.RelocatableFolders]] +deps = ["SHA", "Scratch"] +git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" +registries = "General" +uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" +version = "1.0.1" + + [deps.RelocatableFolders.syntax] + julia_version = "1.0.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "1.0.0" + + [deps.SHA.syntax] + julia_version = "1.0.0" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" +registries = "General" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.3.0" + + [deps.Scratch.syntax] + julia_version = "1.9.0" + +[[deps.StructIO]] +git-tree-sha1 = "c581be48ae1cbf83e899b14c07a807e1787512cc" +registries = "General" +uuid = "53d494c1-5632-5724-8f4c-31dff12d585f" +version = "0.3.1" + + [deps.StructIO.syntax] + julia_version = "1.0.0" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.13.0" + + [deps.StyledStrings.syntax] + julia_version = "1.11.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + + [deps.TOML.syntax] + julia_version = "1.6.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + + [deps.Tar.syntax] + julia_version = "1.3.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + + [deps.UUIDs.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + + [deps.Unicode.syntax] + julia_version = "1.14.0-DEV.2012" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.2+0" + + [deps.Zlib_jll.syntax] + julia_version = "1.6.0" + +[[deps.Zstd_jll]] +deps = ["CompilerSupportLibraries_jll", "Libdl"] +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.7+1" + + [deps.Zstd_jll.syntax] + julia_version = "1.6.0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.68.1+0" + + [deps.nghttp2_jll.syntax] + julia_version = "1.11.0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.8.0+0" + + [deps.p7zip_jll.syntax] + julia_version = "1.6.0" + +[registries.General] +url = "https://github.com/JuliaRegistries/General.git" +uuid = "23338594-aafe-5451-b93e-139f81909106" diff --git a/deps/jlutilities/juliac/Project.toml b/deps/jlutilities/juliac/Project.toml new file mode 100644 index 0000000000000..3b1e7f84d6215 --- /dev/null +++ b/deps/jlutilities/juliac/Project.toml @@ -0,0 +1,5 @@ +[deps] +JuliaC = "acedd4c2-ced6-4a15-accc-2607eb759ba2" + +[sources] +JuliaC = {rev = "main", url = "https://github.com/JuliaLang/JuliaC.jl.git"} diff --git a/deps/zstd.mk b/deps/zstd.mk index ecce416ab3f38..7507341fa82ca 100644 --- a/deps/zstd.mk +++ b/deps/zstd.mk @@ -48,10 +48,13 @@ post-install-zstd: $(build_prefix)/manifest/zstd $(PATCHELF_MANIFEST) [ -e $(build_private_libexecdir)/zstd$(EXE) ] [ -e $(build_private_libexecdir)/zstdmt$(EXE) ] ifeq ($(OS), Darwin) + # zstd is relocated from bin/ to libexec/julia/, so its JLL rpath must be updated to keep finding ../libzstd. + # but this invalidates the existing signature, and macOS can then refuse to launch zstd, so also call codesign. for j in zstd zstdmt ; do \ [ -L $(build_private_libexecdir)/$$j ] && continue; \ install_name_tool -rpath @executable_path/$(reverse_build_private_libexecdir_rel) @loader_path/$(build_libdir_rel) $(build_private_libexecdir)/$$j 2>/dev/null || true; \ install_name_tool -rpath @loader_path/$(build_libdir_rel) @executable_path/$(reverse_build_private_libexecdir_rel) $(build_private_libexecdir)/$$j || exit 1; \ + codesign -s - -f $(build_private_libexecdir)/$$j || exit 1; \ done else ifneq (,$(findstring $(OS),Linux FreeBSD)) for j in zstd zstdmt ; do \ diff --git a/doc/src/devdocs/precompile_hang.md b/doc/src/devdocs/precompile_hang.md index 2204651848509..279ffec5360e8 100644 --- a/doc/src/devdocs/precompile_hang.md +++ b/doc/src/devdocs/precompile_hang.md @@ -17,7 +17,7 @@ If you follow the advice and hit `Ctrl-C`, you might see 1 dependency had warnings during precompilation: β”Œ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982] -β”‚ [pid 2745] waiting for IO to finish: +β”‚ [pid 2745] Waiting for background task / IO / timer to finish: β”‚ Handle type uv_handle_t->data β”‚ timer 0x55580decd1e0->0x7f94c3a4c340 ``` diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 71e331d13e233..9db6d52189d96 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -2148,6 +2148,7 @@ void jl_dump_native_impl(void *native_code, sysimgM.setOverrideStackAlignment(OverrideStackAlignment); int compression = jl_options.compress_sysimage ? 15 : 0; + uint32_t sysimg_checksum = jl_crc32c(0, z->buf, z->size); ArrayRef sysimg_data{z->buf, (size_t)z->size}; SmallVector compressed_data; if (compression) { @@ -2176,6 +2177,10 @@ void jl_dump_native_impl(void *native_code, addComdat(new GlobalVariable(sysimgM, len->getType(), true, GlobalVariable::ExternalLinkage, len, "jl_system_image_size"), TheTriple); + Constant *checksum_val = ConstantInt::get(Type::getInt32Ty(Context), sysimg_checksum); + addComdat(new GlobalVariable(sysimgM, checksum_val->getType(), true, + GlobalVariable::ExternalLinkage, + checksum_val, "jl_system_image_checksum"), TheTriple); const char *unpack_func = compression ? "jl_image_unpack_zstd" : "jl_image_unpack_uncomp"; auto unpack = new GlobalVariable(sysimgM, DL.getIntPtrType(Context), true, diff --git a/src/cgutils.cpp b/src/cgutils.cpp index d99be5ec08315..6cc232197c1d9 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -2087,7 +2087,9 @@ static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_v setName(ctx.emission_context, ok, "boundscheck"); BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); - ctx.builder.CreateCondBr(ok, passBB, failBB); + MDBuilder MDB(ctx.builder.getContext()); + SmallVector Weights{2000, 1}; + ctx.builder.CreateCondBr(ok, passBB, failBB, MDB.createBranchWeights(Weights)); ctx.builder.SetInsertPoint(failBB); if (!ty) { // jl_value_t** tuple (e.g. the vararg) ctx.builder.CreateCall(prepare_call(jlvboundserror_func), { ainfo.V, len, i }); @@ -4736,7 +4738,9 @@ static jl_cgval_t emit_memoryref_direct(jl_codectx_t &ctx, const jl_cgval_t &mem Value *mlen = emit_genericmemorylen(ctx, boxmem, typ); Value *inbound = ctx.builder.CreateICmpULT(idx0, mlen); setName(ctx.emission_context, inbound, "memoryref_isinbounds"); - ctx.builder.CreateCondBr(inbound, endBB, failBB); + MDBuilder MDB(ctx.builder.getContext()); + SmallVector Weights{2000, 1}; + ctx.builder.CreateCondBr(inbound, endBB, failBB, MDB.createBranchWeights(Weights)); failBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(failBB); ctx.builder.CreateCall(prepare_call(jlboundserror_func), @@ -4822,7 +4826,9 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg 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); + MDBuilder MDB(ctx.builder.getContext()); + SmallVector Weights{2000, 1}; + ctx.builder.CreateCondBr(inbound, endBB, failBB, MDB.createBranchWeights(Weights)); failBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(failBB); ctx.builder.CreateCall(prepare_call(jlboundserror_func), @@ -4867,26 +4873,34 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); Value *mptr = emit_genericmemoryptr(ctx, mem, layout, 0); + MDBuilder MDB(ctx.builder.getContext()); + SmallVector Weights{2000, 1}; #if 0 Value *mend = mptr; Value *blen = ctx.builder.CreateMul(mlen, elsz, "", true, true); mend = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), mptr, blen); + Value *notovflw = ctx.builder.CreateNot(ovflw); + BasicBlock *boundscheckBB = BasicBlock::Create(ctx.builder.getContext(), "boundscheck"); + ctx.builder.CreateCondBr(notovflw, boundscheckBB, failBB, MDB.createBranchWeights(Weights)); + boundscheckBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(boundscheckBB); Value *inbound = ctx.builder.CreateAnd( ctx.builder.CreateICmpULE(mptr, newdata), ctx.builder.CreateICmpULT(newdata, mend)); - inbound = ctx.builder.CreateAnd( - ctx.builder.CreateNot(ovflw), - inbound); #elif 1 Value *bidx0 = ctx.builder.CreateSub( 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 *notovflw = ctx.builder.CreateNot(ovflw); + setName(ctx.emission_context, notovflw, "memoryref_notovflw"); + BasicBlock *boundscheckBB = BasicBlock::Create(ctx.builder.getContext(), "boundscheck"); + ctx.builder.CreateCondBr(notovflw, boundscheckBB, failBB, MDB.createBranchWeights(Weights)); + boundscheckBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(boundscheckBB); 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( @@ -4895,7 +4909,7 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg idx0 = ctx.builder.CreateExactUDiv(idx0, elsz); Value *inbound = ctx.builder.CreateICmpULT(idx0, mlen); #endif - ctx.builder.CreateCondBr(inbound, endBB, failBB); + ctx.builder.CreateCondBr(inbound, endBB, failBB, MDB.createBranchWeights(Weights)); failBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(failBB); ctx.builder.CreateCall(prepare_call(jlboundserror_func), diff --git a/src/datatype.c b/src/datatype.c index c08e9f783eb7c..3d1a8b5bb9bf4 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -2335,6 +2335,11 @@ jl_value_t *get_nth_pointer(jl_value_t *v, size_t i) uint32_t npointers = ly->npointers; if (i >= npointers) jl_bounds_error_int(v, i); + if (ly->flags.fielddesc_type == 3) { + // Foreign types can report that they contain pointers for GC purposes, + // but they do not expose an inline pointer-offset table to enumerate. + return NULL; + } const uint8_t *ptrs8 = (const uint8_t *)jl_dt_layout_ptrs(ly); const uint16_t *ptrs16 = (const uint16_t *)jl_dt_layout_ptrs(ly); const uint32_t *ptrs32 = (const uint32_t*)jl_dt_layout_ptrs(ly); diff --git a/src/jl_uv.c b/src/jl_uv.c index 766e962288db6..e41b896320693 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -68,7 +68,7 @@ static void wait_empty_func(uv_timer_t *t) uv_unref((uv_handle_t*)&signal_async); if (!uv_loop_alive(t->loop)) return; - jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n" + jl_safe_printf("\n[pid %zd] Waiting for background task / IO / timer to finish:\n" " Handle type uv_handle_t->data\n", (size_t)uv_os_getpid()); uv_walk(jl_io_loop, walk_print_cb, NULL); diff --git a/src/julia.h b/src/julia.h index b2fd8f66de17c..db24a404dea5f 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2147,6 +2147,7 @@ typedef struct { const char *data; size_t size; uint64_t base; + uint32_t checksum; } jl_image_buf_t; struct _jl_image_t; diff --git a/src/llvm-gc-interface-passes.h b/src/llvm-gc-interface-passes.h index 0d21ea0a66cd8..b41a52cef8454 100644 --- a/src/llvm-gc-interface-passes.h +++ b/src/llvm-gc-interface-passes.h @@ -351,7 +351,7 @@ struct LateLowerGCFrame: private JuliaPassContext { void NoteOperandUses(State &S, BBState &BBS, Instruction &UI); void MaybeTrackDst(State &S, MemTransferInst *MI); - void MaybeTrackStore(State &S, StoreInst *I); + bool MaybeTrackStore(State &S, StoreInst *I); State LocalScan(Function &F); void ComputeLiveness(State &S); void ComputeLiveSets(State &S); diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index ae1351ae41ca1..a727359003914 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1148,44 +1148,44 @@ void LateLowerGCFrame::FixUpRefinements(ArrayRef PHINumbers, State &S) } } -// Look through instructions to find all possible allocas that might become the sret argument -static std::optional> FindSretAllocas(Value* SRetArg) { - SmallSetVector allocas; - if (AllocaInst *OneSRet = dyn_cast(SRetArg)) { - allocas.insert(OneSRet); // Found it directly - } else { - SmallSetVector worklist; - worklist.insert(SRetArg); +// Look through selects and phis to find all possible alloca bases of a pointer. +// Returns an empty set if a non-alloca base is encountered. +static SmallSetVector FindAllocaBases(Value *V) { + SmallSetVector allocas; + if (AllocaInst *AI = dyn_cast(V)) { + allocas.insert(AI); // Found it directly + } + else { + SmallVector worklist; + SmallPtrSet visited; + worklist.push_back(V); + visited.insert(V); while (!worklist.empty()) { - Value *V = worklist.pop_back_val(); - if (AllocaInst *Alloca = dyn_cast(V->stripInBoundsOffsets())) { + Value *W = worklist.pop_back_val(); + if (AllocaInst *Alloca = dyn_cast(W->stripInBoundsOffsets())) { allocas.insert(Alloca); // Found a candidate - } else if (PHINode *Phi = dyn_cast(V)) { + } + else if (PHINode *Phi = dyn_cast(W)) { for (Value *Incoming : Phi->incoming_values()) { - worklist.insert(Incoming); + if (visited.insert(Incoming).second) + worklist.push_back(Incoming); } - } else if (SelectInst *SI = dyn_cast(V)) { - auto TrueBranch = SI->getTrueValue(); - auto FalseBranch = SI->getFalseValue(); - if (TrueBranch && FalseBranch) { - worklist.insert(TrueBranch); - worklist.insert(FalseBranch); - } else { - llvm_dump(SI); - dbgs() << "Malformed Select\n"; - return {}; - } - } else { - llvm_dump(V); - dbgs() << "Unexpected SRet argument\n"; - return {}; + } + else if (SelectInst *SI = dyn_cast(W)) { + if (visited.insert(SI->getTrueValue()).second) + worklist.push_back(SI->getTrueValue()); + if (visited.insert(SI->getFalseValue()).second) + worklist.push_back(SI->getFalseValue()); + } + else { + allocas.clear(); + return allocas; } } } - assert(allocas.size() > 0); - assert(std::all_of(allocas.begin(), allocas.end(), [&] (AllocaInst* SRetAlloca) JL_NOTSAFEPOINT { - return (SRetAlloca->getArraySize() == allocas[0]->getArraySize() && - SRetAlloca->getAllocatedType() == allocas[0]->getAllocatedType()); + assert(std::all_of(allocas.begin(), allocas.end(), [&] (AllocaInst *AI) JL_NOTSAFEPOINT { + return (AI->getArraySize() == allocas[0]->getArraySize() && + AI->getAllocatedType() == allocas[0]->getAllocatedType()); } )); return allocas; @@ -1250,15 +1250,14 @@ State LateLowerGCFrame::LocalScan(Function &F) { auto tracked = CountTrackedPointers(ElT, true); if (tracked.count) { HasDefBefore = true; - auto allocas_opt = FindSretAllocas((CI->arg_begin()[0])->stripInBoundsOffsets()); + auto allocas = FindAllocaBases((CI->arg_begin()[0])->stripInBoundsOffsets()); // We know that with the right optimizations we can forward a sret directly from an argument // This hasn't been seen without adding IPO effects to julia functions but it's possible we need to handle that too // If they are tracked.all we can just pass through but if they have a roots bundle it's possible we need to emit some copies Β―\_(ツ)_/Β― - if (!allocas_opt.has_value()) { + if (allocas.size() == 0) { llvm_dump(&F); abort(); } - auto allocas = allocas_opt.value(); for (AllocaInst *SRet : allocas) { if (!(SRet->isStaticAlloca() && isa(ElT) && ElT->getPointerAddressSpace() == AddressSpace::Tracked)) { assert(!tracked.derived); @@ -1267,12 +1266,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { } else { Value *arg1 = (CI->arg_begin()[1])->stripInBoundsOffsets(); - auto gc_allocas_opt = FindSretAllocas(arg1); - if (!gc_allocas_opt.has_value()) { - llvm_dump(&F); - abort(); - } - auto gc_allocas = gc_allocas_opt.value(); + auto gc_allocas = FindAllocaBases(arg1); if (gc_allocas.size() == 0) { llvm_dump(CI); errs() << "Expected one Alloca at least\n"; @@ -1455,7 +1449,8 @@ State LateLowerGCFrame::LocalScan(Function &F) { } } else if (StoreInst *SI = dyn_cast(&I)) { NoteOperandUses(S, BBS, I); - MaybeTrackStore(S, SI); + if (MaybeTrackStore(S, SI)) + BBS.FirstSafepointAfterFirstDef = BBS.FirstSafepoint; } else if (isa(&I)) { NoteOperandUses(S, BBS, I); } else if (auto *ASCI = dyn_cast(&I)) { @@ -1577,37 +1572,48 @@ SmallVector ExtractTrackedValues(Value *Src, Type *STy, bool isptr, I // return Ptrs.size(); //} -void LateLowerGCFrame::MaybeTrackStore(State &S, StoreInst *I) { +bool LateLowerGCFrame::MaybeTrackStore(State &S, StoreInst *I) { Value *PtrBase = I->getPointerOperand()->stripInBoundsOffsets(); auto tracked = CountTrackedPointers(I->getValueOperand()->getType()); if (!tracked.count) - return; // nothing to track is being stored - if (AllocaInst *AI = dyn_cast(PtrBase)) { + return false; // nothing to track is being stored + // Find all alloca bases, looking through selects and phis. + // LLVM's SROA/InstCombine can merge conditional alloca stores into a + // select/phi over alloca pointers (see #60985). + auto Allocas = FindAllocaBases(PtrBase); + if (Allocas.empty()) + return false; // assume it is rooted--TODO: should we be more conservative? + bool needsTrackedStore = false; + bool Tracked = false; + for (AllocaInst *AI : Allocas) { Type *STy = AI->getAllocatedType(); if (!AI->isStaticAlloca() || (isa(STy) && STy->getPointerAddressSpace() == AddressSpace::Tracked) || S.ArrayAllocas.count(AI)) - return; // already numbered this - auto tracked = CountTrackedPointers(STy); - if (tracked.count) { - assert(!tracked.derived); - if (tracked.all) { + continue; // already numbered this + auto allocaTracked = CountTrackedPointers(STy); + if (allocaTracked.count) { + assert(!allocaTracked.derived); + if (allocaTracked.all) { // track the Alloca directly - S.ArrayAllocas[AI] = tracked.count * cast(AI->getArraySize())->getZExtValue(); - return; + S.ArrayAllocas[AI] = allocaTracked.count * cast(AI->getArraySize())->getZExtValue(); + Tracked = true; + continue; } } - } - else { - return; // assume it is rooted--TODO: should we be more conservative? - } - // track the Store with a Shadow - //auto &Shadow = S.ShadowAllocas[AI]; - //if (!Shadow) - // Shadow = new AllocaInst(ArrayType::get(T_prjlvalue, tracked.count), 0, "", MI); - //AI = Shadow; - //Value *Src = I->getValueOperand(); - //unsigned count = TrackWithShadow(Src, Src->getType(), false, AI, MI, TODO which slots are we actually clobbering?); - //assert(count == tracked.count); (void)count; - S.TrackedStores.push_back(std::make_pair(I, tracked.count)); + needsTrackedStore = true; + } + if (needsTrackedStore) { + // track the Store with a Shadow + //auto &Shadow = S.ShadowAllocas[AI]; + //if (!Shadow) + // Shadow = new AllocaInst(ArrayType::get(T_prjlvalue, tracked.count), 0, "", MI); + //AI = Shadow; + //Value *Src = I->getValueOperand(); + //unsigned count = TrackWithShadow(Src, Src->getType(), false, AI, MI, TODO which slots are we actually clobbering?); + //assert(count == tracked.count); (void)count; + S.TrackedStores.push_back(std::make_pair(I, tracked.count)); + Tracked = true; + } + return Tracked; } /* diff --git a/src/staticdata.c b/src/staticdata.c index 053b0b37603b4..f16f4c6fe7411 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -3551,20 +3551,26 @@ static void jl_prefetch_system_image(const char *data, size_t size) JL_DLLEXPORT void jl_image_unpack_uncomp(void *handle, jl_image_buf_t *image) { size_t *plen; + uint32_t *pchecksum; jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1, 0); jl_dlsym(handle, "jl_system_image_data", (void **)&image->data, 1, 0); jl_dlsym(handle, "jl_image_pointers", (void**)&image->pointers, 1, 0); + jl_dlsym(handle, "jl_system_image_checksum", (void **)&pchecksum, 1, 0); image->size = *plen; + image->checksum = *pchecksum; jl_prefetch_system_image(image->data, image->size); } JL_DLLEXPORT void jl_image_unpack_zstd(void *handle, jl_image_buf_t *image) { size_t *plen; + uint32_t *pchecksum; const char *data; jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1, 0); jl_dlsym(handle, "jl_system_image_data", (void **)&data, 1, 0); jl_dlsym(handle, "jl_image_pointers", (void **)&image->pointers, 1, 0); + jl_dlsym(handle, "jl_system_image_checksum", (void **)&pchecksum, 1, 0); + image->checksum = *pchecksum; jl_prefetch_system_image(data, *plen); image->size = ZSTD_getFrameContentSize(data, *plen); size_t page_size = jl_getpagesize(); /* jl_page_size is not set yet when loading sysimg */ @@ -4390,7 +4396,7 @@ static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *im sysimg = &f->buf[f->bpos]; if (needs_permalloc) success = ios_readall(f, sysimg, len) == len; - if (!success || jl_crc32c(0, sysimg, len) != (uint32_t)checksum) { + if (!success) { restored = jl_get_exceptionf(jl_errorexception_type, "Error reading package image file."); JL_SIGATOMIC_END(); } @@ -4495,8 +4501,7 @@ JL_DLLEXPORT void jl_restore_system_image(jl_image_t *image, jl_image_buf_t buf) JL_SIGATOMIC_BEGIN(); ios_static_buffer(&f, (char *)buf.data, buf.size); - uint32_t checksum = jl_crc32c(0, buf.data, buf.size); - jl_restore_system_image_from_stream(&f, image, checksum); + jl_restore_system_image_from_stream(&f, image, buf.checksum); ios_close(&f); JL_SIGATOMIC_END(); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 285dcfc1dabe8..2542f6fc9f537 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -720,14 +720,11 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - jl_value_t *depots = NULL, *prefs_hash = NULL, *prefs_list = NULL; + jl_value_t *depots = NULL, *prefs_blob = NULL; jl_value_t *unique_func = NULL; jl_value_t *replace_depot_func = NULL; jl_value_t *normalize_depots_func = NULL; - jl_value_t *toplevel = NULL; - jl_value_t *prefs_hash_func = NULL; - jl_value_t *get_compiletime_prefs_func = NULL; - JL_GC_PUSH8(&depots, &prefs_list, &unique_func, &replace_depot_func, &normalize_depots_func, &toplevel, &prefs_hash_func, &get_compiletime_prefs_func); + JL_GC_PUSH5(&depots, &prefs_blob, &unique_func, &replace_depot_func, &normalize_depots_func); jl_array_t *udeps = (jl_array_t*)jl_get_global_value(jl_base_module, jl_symbol("_require_dependencies"), ct->world_age); *udepsp = udeps; @@ -798,51 +795,22 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t } write_int32(s, 0); // terminator, for ease of reading - // Calculate Preferences hash for current package. - if (jl_base_module) { - // Toplevel module is the module we're currently compiling, use it to get our preferences hash - toplevel = jl_get_global_value(jl_base_module, jl_symbol("__toplevel__"), ct->world_age); - prefs_hash_func = jl_eval_global_var(jl_base_module, jl_symbol("get_preferences_hash"), ct->world_age); - get_compiletime_prefs_func = jl_eval_global_var(jl_base_module, jl_symbol("get_compiletime_preferences"), ct->world_age); - - if (toplevel) { - // call get_compiletime_prefs(__toplevel__) - jl_value_t *args[3] = {get_compiletime_prefs_func, (jl_value_t*)toplevel, NULL}; - prefs_list = (jl_value_t*)jl_apply(args, 2); - JL_TYPECHK(write_dependency_list, array, prefs_list); - - // Call get_preferences_hash(__toplevel__, prefs_list) - args[0] = prefs_hash_func; - args[2] = prefs_list; - prefs_hash = (jl_value_t*)jl_apply(args, 3); - JL_TYPECHK(write_dependency_list, uint64, prefs_hash); - } - } + // Serialize any compile-time dependencies on preferences. + assert(jl_base_module); + jl_value_t *get_preferences_blob = jl_eval_global_var( + jl_base_module, jl_symbol("get_preferences_blob"), ct->world_age); + assert(get_preferences_blob); + jl_value_t *args[1] = { get_preferences_blob }; + prefs_blob = jl_apply(args, 1); + JL_TYPECHK(write_dependency_list, string, prefs_blob); ct->world_age = last_age; - // If we successfully got the preferences, write it out, otherwise write `0` for this `.ji` file. - if (prefs_hash != NULL && prefs_list != NULL) { - size_t i, l = jl_array_nrows(prefs_list); - for (i = 0; i < l; i++) { - jl_value_t *pref_name = jl_array_ptr_ref(prefs_list, i); - JL_TYPECHK(write_dependency_list, string, pref_name); - size_t slen = jl_string_len(pref_name); - write_int32(s, slen); - ios_write(s, jl_string_data(pref_name), slen); - } - write_int32(s, 0); // terminator - write_uint64(s, jl_unbox_uint64(prefs_hash)); - } - else { - // This is an error path, but let's at least generate a valid `.ji` file. - // We declare an empty list of preference names, followed by a zero-hash. - // The zero-hash is not what would be generated for an empty set of preferences, - // and so this `.ji` file will be invalidated by a future non-erroring pass - // through this function. - write_int32(s, 0); - write_uint64(s, 0); - } - JL_GC_POP(); // for depots, prefs_list + // Write out the serialized preferences "blob" + size_t slen = jl_string_len(prefs_blob); + write_int32(s, slen); + ios_write(s, jl_string_data(prefs_blob), slen); + + JL_GC_POP(); // for depots, prefs_blob #undef jl_is_deptuple // write a dummy file position to indicate the beginning of the source-text diff --git a/src/subtype.c b/src/subtype.c index e7a426ae7bc79..2442986e77c74 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -68,7 +68,7 @@ typedef struct jl_varbinding_t { jl_tvar_t *var; // store NULL to "delete" this from env (temporarily) jl_value_t *JL_NONNULL lb; jl_value_t *JL_NONNULL ub; - int8_t right; // whether this variable came from the right side of `A <: B` + int8_t existential; // whether this variable should be treated as existential int8_t occurs_inv; // occurs in invariant position int8_t occurs_cov; // # of occurrences in covariant position int8_t concrete; // 1 if another variable has a constraint forcing this one to be concrete @@ -679,6 +679,37 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param); static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param, int limit_slow); +static int is_leaf_typevar(jl_tvar_t *v) JL_NOTSAFEPOINT; + +// Check whether env (variable bounds & diagonality) changed compared to saved env. +static int env_unchanged(jl_stenv_t *e, jl_savedenv_t *se) JL_NOTSAFEPOINT +{ + jl_value_t **roots = NULL; + if (se->gcframe.nroots == JL_GC_ENCODE_PUSHARGS(1)) { + jl_svec_t *sv = (jl_svec_t*)se->roots[0]; + assert(jl_is_svec(sv)); + roots = jl_svec_data(sv); + } + else if (se->gcframe.nroots) { + roots = se->roots; + } + jl_varbinding_t *v = e->vars; + int i = 0, j = 1; + while (v != NULL) { + assert(roots != NULL); + if (v->existential) { + if (v->lb != roots[i] || v->ub != roots[i + 1]) + return 0; // check if bounds changed + if (is_leaf_typevar(v->var) && v->occurs_inv == 0 && v->occurs_cov > 1 && se->buf[j] <= 1) + return 0; // check if a variable became digonal from non-diagonal + } + i += 3; // lb, ub, innervars + j += 3; + v = v->prev; + } + return 1; +} + // subtype for variable bounds consistency check. needs its own forall/exists environment. static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { @@ -694,6 +725,8 @@ static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) return 1; if (x == (jl_value_t*)jl_any_type && jl_is_datatype(y)) return 0; + if (obviously_in_union(y, x)) + return 1; jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); int sub = local_forall_exists_subtype(x, y, e, 0, 1); pop_unionstate(&e->Lunions, &oldLunions); @@ -764,7 +797,7 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) if (e->Loffset != 0 && !jl_is_typevar(a) && a != jl_bottom_type && a != (jl_value_t *)jl_any_type) return 0; - if (!bb->right) // check βˆ€b . b<:a + if (!bb->existential) // check βˆ€b . b<:a return subtype_left_var(bb->ub, a, e, param); if (bb->ub == a) return 1; @@ -785,7 +818,7 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) assert(bb->ub != (jl_value_t*)b); if (jl_is_typevar(a)) { jl_varbinding_t *aa = lookup(e, (jl_tvar_t*)a); - if (aa && !aa->right && in_union(bb->lb, a) && bb->depth0 != aa->depth0 && var_outside(e, b, (jl_tvar_t*)a)) { + if (aa && !aa->existential && in_union(bb->lb, a) && bb->depth0 != aa->depth0 && var_outside(e, b, (jl_tvar_t*)a)) { // an "exists" var cannot equal a "forall" var inside it unless the forall // var has equal bounds. return subtype_left_var(aa->ub, aa->lb, e, param); @@ -805,7 +838,7 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) if (e->Loffset != 0 && !jl_is_typevar(a) && a != jl_bottom_type && a != (jl_value_t *)jl_any_type) return 0; - if (!bb->right) // check βˆ€b . b>:a + if (!bb->existential) // check βˆ€b . b>:a return subtype_left_var(a, bb->lb, e, param); if (bb->lb == a) return 1; @@ -820,7 +853,7 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) assert(bb->lb != (jl_value_t*)b); if (jl_is_typevar(a)) { jl_varbinding_t *aa = lookup(e, (jl_tvar_t*)a); - if (aa && !aa->right && bb->depth0 != aa->depth0 && param == 2 && var_outside(e, b, (jl_tvar_t*)a)) + if (aa && !aa->existential && bb->depth0 != aa->depth0 && param == 2 && var_outside(e, b, (jl_tvar_t*)a)) return subtype_left_var(aa->ub, aa->lb, e, param); } return 1; @@ -1339,7 +1372,7 @@ static int subtype_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, in } if (vvx != JL_VARARG_NONE && vvx != JL_VARARG_INT && (!xbb || !jl_is_long(xbb->lb))) { - if (vvx == JL_VARARG_UNBOUND || (xbb && !xbb->right)) { + if (vvx == JL_VARARG_UNBOUND || (xbb && !xbb->existential)) { // Unbounded on the LHS, bounded on the RHS if (vvy == JL_VARARG_NONE || vvy == JL_VARARG_INT) return 0; @@ -1379,7 +1412,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; +static int has_existential_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). @@ -1410,7 +1443,7 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // 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)) + if (yb == NULL || !yb->existential || !has_existential_typevar(yb->ub, e)) return subtype_var(yvar, x, e, 1, param); } x = pick_union_element(x, e, 0); @@ -1428,7 +1461,7 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // variables lurking inside. // note: for forall var, there's no need to split y if it has no free typevars. jl_varbinding_t *xx = lookup(e, (jl_tvar_t *)x); - ui = ((xx && xx->right) || jl_has_free_typevars(y)) && pick_union_decision(e, 1); + ui = ((xx && xx->existential) || jl_has_free_typevars(y)) && pick_union_decision(e, 1); } if (ui == 1) y = pick_union_element(y, e, 1); @@ -1449,8 +1482,8 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) if (yub == ylb && jl_is_typevar(yub)) return subtype(x, yub, e, param); } - int xr = xx && xx->right; // treat free variables as "forall" (left) - int yr = yy && yy->right; + int xr = xx && xx->existential; // treat free variables as "forall" (left) + int yr = yy && yy->existential; if (xr) { if (yy) record_var_occurrence(yy, e, param); if (yr) { @@ -1472,7 +1505,7 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) if (jl_is_unionall(y)) { jl_varbinding_t *xb = lookup(e, (jl_tvar_t*)x); jl_value_t *xub = xb == NULL ? ((jl_tvar_t *)x)->ub : xb->ub; - if ((xb == NULL ? !e->ignore_free : !xb->right) && xub != y) { + if ((xb == NULL ? !e->ignore_free : !xb->existential) && xub != y) { // We'd better unwrap `y::UnionAll` eagerly if `x` isa βˆ€-var. // This makes sure the following cases work correct: // 1) `βˆ€T <: Union{βˆƒS, SomeType{P}} where {P}`: `S == Any` ==> `S >: T` @@ -1591,20 +1624,20 @@ static int is_definite_length_tuple_type(jl_value_t *x) return k == JL_VARARG_NONE || k == JL_VARARG_INT; } -static int is_exists_typevar(jl_value_t *x, jl_stenv_t *e) +static int is_existential_typevar(jl_value_t *x, jl_stenv_t *e) { if (!jl_is_typevar(x)) return 0; jl_varbinding_t *vb = lookup(e, (jl_tvar_t *)x); - return vb && vb->right; + return vb && vb->existential; } -static int has_exists_typevar(jl_value_t *x, jl_stenv_t *e) JL_NOTSAFEPOINT +static int has_existential_typevar(jl_value_t *x, jl_stenv_t *e) JL_NOTSAFEPOINT { jl_typeenv_t *env = NULL; jl_varbinding_t *v = e->vars; while (v != NULL) { - if (v->right) { + if (v->existential) { jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); newenv->var = v->var; newenv->val = NULL; @@ -1629,8 +1662,8 @@ static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t int kindy = !jl_has_free_typevars(y); if (kindx && kindy) return jl_subtype(x, y); - int has_exists = (!kindx && has_exists_typevar(x, e)) || - (!kindy && has_exists_typevar(y, e)); + int has_exists = (!kindx && has_existential_typevar(x, e)) || + (!kindy && has_existential_typevar(y, e)); if (!has_exists) { // We can use βˆ€_βˆƒ_subtype safely for βˆƒ free inputs. // This helps to save some bits in union stack. @@ -1642,7 +1675,7 @@ static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t pop_unionstate(&e->Runions, &oldRunions); return sub; } - if (is_exists_typevar(x, e) != is_exists_typevar(y, e)) { + if (is_existential_typevar(x, e) != is_existential_typevar(y, e)) { e->Lunions.used = 0; while (1) { e->Lunions.more = 0; @@ -1676,8 +1709,6 @@ static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t break; if (limited || e->Runions.more == oldRmore) { // re-save env and freeze the βˆƒdecision for previous βˆ€Union - // Note: We could ignore the rest `βˆƒUnion` decisions if `x` and `y` - // contain no βˆƒ typevar, as they have no effect on env. ini_count = count; push_unionstate(&latestLunions, &e->Lunions); re_save_env(e, &se, 1); @@ -1693,7 +1724,9 @@ static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t } if (!sub) assert(e->Runions.more == oldRmore); - else if (limited) + else if (e->Runions.more > oldRmore && (limited || env_unchanged(e, &se))) + // Ignore the rest βˆƒUnion decisions if env is unchanged/limited. + // As otherwise it might cause combinatorial explosion without making any difference to the result. e->Runions.more = oldRmore; free_env(&se); return sub; @@ -1713,7 +1746,7 @@ static int equal_var(jl_tvar_t *v, jl_value_t *x, jl_stenv_t *e) return e->ignore_free || ( local_forall_exists_subtype(x, v->lb, e, 2, !jl_has_free_typevars(x)) && local_forall_exists_subtype(v->ub, x, e, 0, 0)); - if (!vb->right) + if (!vb->existential) return local_forall_exists_subtype(x, vb->lb, e, 2, !jl_has_free_typevars(x)) && local_forall_exists_subtype(vb->ub, x, e, 0, 0); if (vb->lb == x) @@ -2720,14 +2753,14 @@ static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t * jl_varbinding_t *v = e->vars; int n = 0; while (v != NULL) { - rs[n++] = v->right; - v->right = 1; + rs[n++] = v->existential; + v->existential = 1; v = v->prev; } int issub = subtype_in_env(x, y, e); n = 0; v = e->vars; while (v != NULL) { - v->right = rs[n++]; + v->existential = rs[n++]; v = v->prev; } return issub; @@ -3349,7 +3382,7 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind } } - if (vb->right && e->envidx < e->envsz) { + if (vb->existential && e->envidx < e->envsz) { jl_value_t *oldval = e->envout[e->envidx]; if (!varval || (!is_leaf_bound(varval) && !vb->occurs_inv)) e->envout[e->envidx] = (jl_value_t*)vb->var; @@ -3770,7 +3803,7 @@ static void flip_vars(jl_stenv_t *e) { jl_varbinding_t *btemp = e->vars; while (btemp != NULL) { - btemp->right = !btemp->right; + btemp->existential = !btemp->existential; btemp = btemp->prev; } } diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index 9a0729c50d01f..9aec22b58bf10 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -134,15 +134,13 @@ end # Conform to LazyJLLWrappers API function eager_mode() - if @isdefined(libatomic) - dlopen(libatomic) - end + dlopen(libatomic) dlopen(libgcc_s) dlopen(libgomp) - if @isdefined libquadmath + @static if @isdefined libquadmath dlopen(libquadmath) end - if @isdefined libssp + @static if @isdefined libssp dlopen(libssp) end dlopen(libgfortran) @@ -154,10 +152,10 @@ function __init__() global libatomic_path = string(libatomic.path) global libgcc_s_path = string(libgcc_s.path) global libgomp_path = string(libgomp.path) - if @isdefined libquadmath_path + @static if @isdefined libquadmath_path global libquadmath_path = string(libquadmath.path) end - if @isdefined libssp_path + @static if @isdefined libssp_path global libssp_path = string(libssp.path) end global libgfortran_path = string(libgfortran.path) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 6786860f94b70..20efd9bd6cb93 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -671,7 +671,7 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf full_input = String(buf.data[1:buf.size]) if !isempty(full_input) passes = StylingPass[] - context = StylingContext(buf_pos, regstart, regstop) + context = StylingContext(buf_pos + 1, regstart + 1, regstop) # StylingContext positions are 1-based # Add prompt-specific styling passes if the prompt has them and styling is enabled enable_style_input = prompt_obj === nothing ? false : diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index d641853eb4c7a..b45bcf2f92585 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -710,7 +710,7 @@ mutable struct BasicREPL <: AbstractREPL end outstream(r::BasicREPL) = r.terminal -hascolor(r::BasicREPL) = hascolor(r.terminal) +hascolor(r::BasicREPL) = Terminals.hascolor(r.terminal) function run_frontend(repl::BasicREPL, backend::REPLBackendRef) repl.frontend_task = current_task() diff --git a/stdlib/REPL/src/StylingPasses.jl b/stdlib/REPL/src/StylingPasses.jl index 3606566d341ea..04c3bef6bda40 100644 --- a/stdlib/REPL/src/StylingPasses.jl +++ b/stdlib/REPL/src/StylingPasses.jl @@ -137,7 +137,7 @@ function find_enclosing_parens(content::String, ast, cursor_pos::Int) elseif depthchange < 0 && !isempty(paren_stack) # Closing paren - pop from stack and check if cursor is inside open_pos, depth, open_ptype = pop!(paren_stack) - if open_ptype == ptype && open_pos <= cursor_pos < pos + if open_ptype == ptype && open_pos <= cursor_pos <= pos # Cursor is inside this paren pair - keep only innermost per type # Only update if this is the first pair or if it's smaller (more inner) than existing if !haskey(innermost_pairs, ptype) || (pos - open_pos) < (innermost_pairs[ptype][2] - innermost_pairs[ptype][1]) diff --git a/stdlib/REPL/src/precompile.jl b/stdlib/REPL/src/precompile.jl index e0290cdecd680..4b095c8d5c049 100644 --- a/stdlib/REPL/src/precompile.jl +++ b/stdlib/REPL/src/precompile.jl @@ -17,7 +17,7 @@ end function repl_workload() # these are intentionally triggered allowed_errors = [ - "BoundsError: attempt to access 0-element Vector{Any} at index [1]", + "BoundsError: attempt to access 0-element", "MethodError: no method matching f(::$Int, ::$Int)", "Padding of type", # reinterpret docstring has ERROR examples ] diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index b93c3371306b6..ba3de3b2e557e 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -2039,3 +2039,9 @@ end end end end + +@testset "REPL.hascolor(::BasicREPL)" begin + term = REPL.Terminals.TTYTerminal("dumb",IOBuffer("1+2\n"),IOContext(IOBuffer(),:foo=>true),IOBuffer()) + r = REPL.BasicREPL(term) + @test !REPL.hascolor(r) +end diff --git a/stdlib/Sockets/test/runtests.jl b/stdlib/Sockets/test/runtests.jl index 37921371b3bc5..00ffec12ffda4 100644 --- a/stdlib/Sockets/test/runtests.jl +++ b/stdlib/Sockets/test/runtests.jl @@ -556,12 +556,14 @@ end fetch(r) end - let addr = Sockets.InetAddr(ip"192.0.2.5", 4444) + let addr = Sockets.InetAddr(ip"127.0.0.1", 4444) + srv = listen(addr) s = Sockets.TCPSocket() Sockets.connect!(s, addr) r = @async close(s) @test_throws Base._UVError("connect", Base.UV_ECANCELED) Sockets.wait_connected(s) fetch(r) + close(srv) end end diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 86daeecdabe21..5bda1db7ea530 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = release-1.13 -SPARSEARRAYS_SHA1 = 6b7e9ef30624f25d97bce047ad781a25bebe5551 +SPARSEARRAYS_SHA1 = 5307f25727e4ea6506a740a2d99566f3e65cf105 SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 diff --git a/stdlib/Zstd_jll/test/runtests.jl b/stdlib/Zstd_jll/test/runtests.jl index 5cfa2a1375c73..f0b6be5aac0f4 100644 --- a/stdlib/Zstd_jll/test/runtests.jl +++ b/stdlib/Zstd_jll/test/runtests.jl @@ -4,4 +4,7 @@ using Test, Zstd_jll @testset "Zstd_jll" begin @test ccall((:ZSTD_versionNumber, libzstd), Cuint, ()) == 1_05_07 + let version = read(`$(zstd()) --version`, String) + @test contains(version, "Zstandard CLI") + end end diff --git a/test/channels.jl b/test/channels.jl index 721eb478bd13a..c38574a8e08f2 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -463,14 +463,16 @@ end cb = first(async.cond.waitq) @test isopen(async) ccall(:uv_async_send, Cvoid, (Ptr{Cvoid},), async) + ccall(:uv_async_send, Cvoid, (Ptr{Cvoid},), async) + W = Base.workqueue_for(Threads.threadid()) + @test isempty(W) Base.process_events() # schedule event Sys.iswindows() && Base.process_events() # schedule event (windows?) + @test length(W) == 1 ccall(:uv_async_send, Cvoid, (Ptr{Cvoid},), async) @test tc[] == 0 yield() # consume event @test tc[] == 1 - ccall(:uv_async_send, Cvoid, (Ptr{Cvoid},), async) - Base.process_events() Sys.iswindows() && Base.process_events() # schedule event (windows?) yield() # consume event @test tc[] == 2 diff --git a/test/gcext/gcext-test.jl b/test/gcext/gcext-test.jl index 11b0504b71c4d..eb8f0402ddcd5 100644 --- a/test/gcext/gcext-test.jl +++ b/test/gcext/gcext-test.jl @@ -66,6 +66,9 @@ end GC.gc(true) end @test Base.invokelatest(Foreign.get_nmark) > 0 + # Bugfix: the following used to crash + summarysize = Base.summarysize(Foreign.FObj()) + @test summarysize >= sizeof(Ptr) @time Base.invokelatest(Foreign.test, 10) GC.gc(true) @test Base.invokelatest(Foreign.get_nsweep) > 0 diff --git a/test/llvmpasses/late-lower-gc-select-store.ll b/test/llvmpasses/late-lower-gc-select-store.ll new file mode 100644 index 0000000000000..39a82bff139a8 --- /dev/null +++ b/test/llvmpasses/late-lower-gc-select-store.ll @@ -0,0 +1,72 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame)' -S %s | FileCheck %s + +; Test that stores of GC-tracked values through select/phi of alloca pointers +; are properly rooted. This is a regression test for +; https://github.com/JuliaLang/julia/issues/60985 + +declare ptr @julia.get_pgcstack() + +declare void @safepoint() "julia.safepoint" + +; Store through a select of two allocas +define void @store_select(ptr addrspace(10) %val, i1 %cond) { + ; CHECK-LABEL: @store_select + ; CHECK: %gcframe = call ptr @julia.new_gc_frame(i32 4) + %pgcstack = call ptr @julia.get_pgcstack() + %alloca1 = alloca [2 x ptr addrspace(10)], align 8 + %alloca2 = alloca [2 x ptr addrspace(10)], align 8 + %sel = select i1 %cond, ptr %alloca1, ptr %alloca2 + store ptr addrspace(10) %val, ptr %sel, align 8 + call void @safepoint() + ; CHECK: call void @julia.pop_gc_frame(ptr %gcframe) + ret void +} + +; Store through a phi with a cycle (phi references itself via loop back-edge). +; This is a regression test for an infinite loop in FindAllocaBases. +define void @store_phi_cycle(ptr addrspace(10) %val, i1 %cond) { +top: + ; CHECK-LABEL: @store_phi_cycle + ; CHECK: %gcframe = call ptr @julia.new_gc_frame(i32 4) + %pgcstack = call ptr @julia.get_pgcstack() + %alloca1 = alloca [2 x ptr addrspace(10)], align 8 + %alloca2 = alloca [2 x ptr addrspace(10)], align 8 + br label %loop + +loop: + %phi = phi ptr [ %alloca1, %top ], [ %sel, %loop ] + %sel = select i1 %cond, ptr %phi, ptr %alloca2 + store ptr addrspace(10) %val, ptr %sel, align 8 + call void @safepoint() + br i1 %cond, label %loop, label %exit + +exit: + ; CHECK: call void @julia.pop_gc_frame(ptr %gcframe) + ret void +} + +; Store through a phi of two allocas +define void @store_phi(ptr addrspace(10) %val, i1 %cond) { +top: + ; CHECK-LABEL: @store_phi + ; CHECK: %gcframe = call ptr @julia.new_gc_frame(i32 4) + %pgcstack = call ptr @julia.get_pgcstack() + %alloca1 = alloca [2 x ptr addrspace(10)], align 8 + %alloca2 = alloca [2 x ptr addrspace(10)], align 8 + br i1 %cond, label %left, label %right + +left: + br label %merge + +right: + br label %merge + +merge: + %phi = phi ptr [ %alloca1, %left ], [ %alloca2, %right ] + store ptr addrspace(10) %val, ptr %phi, align 8 + call void @safepoint() + ; CHECK: call void @julia.pop_gc_frame(ptr %gcframe) + ret void +} diff --git a/test/loading.jl b/test/loading.jl index b203d77761c0d..40200f036975b 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -275,7 +275,7 @@ end pkg = recurse_package(n...) @test pkg == PkgId(UUID(uuid), n[end]) @test joinpath(@__DIR__, normpath(path)) == locate_package(pkg) - @test Base.compilecache_path(pkg, UInt64(0)) == Base.compilecache_path(pkg, UInt64(0)) + @test Base.compilecache_path(pkg, "") == Base.compilecache_path(pkg, "") end @test identify_package("Baz") === nothing @test identify_package("Qux") === nothing @@ -1105,6 +1105,190 @@ end end end +@testset "Preferences blob" begin + # Tests for get_preferences_blob() and stale_prefs() - the serialization of + # compile-time preference observations into a TOML blob embedded in cache files. + pkg_uuid = uuid4() + pkg2_uuid = uuid4() + mktempdir() do dir + # Set up a project with preferences for two packages + write(joinpath(dir, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + PkgB = "$(pkg2_uuid)" + + [preferences.PkgA] + key1 = "val1" + key2 = 42 + + [preferences.PkgB] + flag = true + """) + + old_load_path = copy(LOAD_PATH) + old_compiletime_prefs = Dict(k => copy(v) for (k, v) in Base.COMPILETIME_PREFERENCES) + try + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + empty!(Base.COMPILETIME_PREFERENCES) + + # Empty COMPILETIME_PREFERENCES β†’ empty blob (fast-path) + @test Base.get_preferences_blob() == "" + + # A queried+set preference appears in the main table + Base.record_compiletime_preference(pkg_uuid, "key1") + blob = Base.get_preferences_blob() + @test !isempty(blob) + parsed = Base.TOML.parse(Base.TOML.Parser{nothing}(blob)) + @test parsed[string(pkg_uuid)]["key1"] == "val1" + @test !haskey(parsed, "unset") + + # A queried+unset preference appears in the [unset] table + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "missing_key") + blob = Base.get_preferences_blob() + parsed = Base.TOML.parse(Base.TOML.Parser{nothing}(blob)) + @test haskey(parsed, "unset") + @test "missing_key" in parsed["unset"][string(pkg_uuid)] + @test !haskey(get(parsed, string(pkg_uuid), Dict()), "missing_key") + + # Mix of set and unset preferences for the same UUID + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "key1") + Base.record_compiletime_preference(pkg_uuid, "missing_key") + blob = Base.get_preferences_blob() + parsed = Base.TOML.parse(Base.TOML.Parser{nothing}(blob)) + @test parsed[string(pkg_uuid)]["key1"] == "val1" + @test "missing_key" in parsed["unset"][string(pkg_uuid)] + + # Multiple UUIDs are tracked independently + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "key1") + Base.record_compiletime_preference(pkg2_uuid, "flag") + blob = Base.get_preferences_blob() + parsed = Base.TOML.parse(Base.TOML.Parser{nothing}(blob)) + @test parsed[string(pkg_uuid)]["key1"] == "val1" + @test parsed[string(pkg2_uuid)]["flag"] == true + + # stale_prefs: empty blob is never stale + @test !Base.stale_prefs("") + + # stale_prefs: set preference unchanged β†’ not stale + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "key1") + blob = Base.get_preferences_blob() + @test !Base.stale_prefs(blob) + + # stale_prefs: set preference value changed β†’ stale + mktempdir() do dir2 + write(joinpath(dir2, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + + [preferences.PkgA] + key1 = "different" + """) + copy!(LOAD_PATH, [joinpath(dir2, "Project.toml")]) + @test Base.stale_prefs(blob) + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + end + + # stale_prefs: set preference becomes unset β†’ stale + mktempdir() do dir2 + write(joinpath(dir2, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + """) + copy!(LOAD_PATH, [joinpath(dir2, "Project.toml")]) + @test Base.stale_prefs(blob) + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + end + + # stale_prefs: unset preference still unset β†’ not stale + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "missing_key") + blob_unset = Base.get_preferences_blob() + @test !Base.stale_prefs(blob_unset) + + # stale_prefs: unset preference becomes set β†’ stale + mktempdir() do dir2 + write(joinpath(dir2, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + + [preferences.PkgA] + missing_key = "now_set" + """) + copy!(LOAD_PATH, [joinpath(dir2, "Project.toml")]) + @test Base.stale_prefs(blob_unset) + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + end + + # hash collision test: + # https://github.com/JuliaLang/julia/issues/59345#issue-3338450603 + # + # `1`, `1.0`, and `true` are all `isequal` in Julia, so == / isequal / hash + # cannot distinguish these semantically distinct values, but the preferences + # system should distinguish them since they are clearly different objects + mktempdir() do dir_bool + write(joinpath(dir_bool, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + + [preferences.PkgA] + flag = true + """) + copy!(LOAD_PATH, [joinpath(dir_bool, "Project.toml")]) + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "flag") + blob_bool = Base.get_preferences_blob() + + mktempdir() do dir_int + write(joinpath(dir_int, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + + [preferences.PkgA] + flag = 1 + """) + copy!(LOAD_PATH, [joinpath(dir_int, "Project.toml")]) + @test Base.stale_prefs(blob_bool) + end + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + end + + # hash collision test (on β‰₯1.13, where hash(false, UInt(0)) == UInt(0)) + # + # the old hash-based approach for preference invalidation would fail to detect + # the difference between `pref = false` and "no preference". + mktempdir() do dir2 + write(joinpath(dir2, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + + [preferences.PkgA] + key1 = false + """) + copy!(LOAD_PATH, [joinpath(dir2, "Project.toml")]) + empty!(Base.COMPILETIME_PREFERENCES) + Base.record_compiletime_preference(pkg_uuid, "key1") + blob_false = Base.get_preferences_blob() + mktempdir() do dir3 + write(joinpath(dir3, "Project.toml"), """ + [deps] + PkgA = "$(pkg_uuid)" + """) + copy!(LOAD_PATH, [joinpath(dir3, "Project.toml")]) + @test Base.stale_prefs(blob_false) + end + copy!(LOAD_PATH, [joinpath(dir, "Project.toml")]) + end + finally + copy!(LOAD_PATH, old_load_path) + empty!(Base.COMPILETIME_PREFERENCES) + merge!(Base.COMPILETIME_PREFERENCES, old_compiletime_prefs) + end + end +end @testset "Loading with incomplete manifest/depot #45977" begin mktempdir() do tmp @@ -1515,10 +1699,8 @@ end """) write(joinpath(foo_path, "Manifest.toml"), """ - # This file is machine-generated - editing it directly is not advised - julia_version = "1.13.0-DEV" + julia_version = "1.13.0" manifest_format = "2.0" - project_hash = "8699765aeeac181c3e5ddbaeb9371968e1f84d6b" [[deps.Foo51989]] path = "." @@ -1580,15 +1762,6 @@ end "JULIA_DEPOT_PATH" => string(depot * Base.Filesystem.pathsep(), s), )) end - mktempdir() do depot - # This manifest has a LibGit2 entry that has a LibGit2_jll with a git-tree-hash1 - # which simulates an old manifest where LibGit2_jll was not a stdlib - badmanifest_test_dir2 = joinpath(@__DIR__, "project", "deps", "BadStdlibDeps2") - @test success(addenv( - `$(Base.julia_cmd()) --project=$badmanifest_test_dir2 --startup-file=no -e 'using LibGit2'`, - "JULIA_DEPOT_PATH" => depot * Base.Filesystem.pathsep(), - )) - end end @testset "code coverage disabled during precompilation" begin diff --git a/test/precompile.jl b/test/precompile.jl index 127ec7a17e312..5f000d218f31f 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -643,8 +643,7 @@ precompile_test_harness(false) do dir """) cachefile, _ = @test_logs (:debug, r"Generating object cache file for FooBar") min_level=Logging.Debug match_mode=:any Base.compilecache(Base.PkgId("FooBar")) - empty_prefs_hash = Base.get_preferences_hash(nothing, String[]) - @test cachefile == Base.compilecache_path(Base.PkgId("FooBar"), empty_prefs_hash) + @test cachefile == Base.compilecache_path(Base.PkgId("FooBar"), "") @test isfile(joinpath(cachedir, "FooBar.ji")) Tsc = Bool(Base.JLOptions().use_pkgimages) ? Tuple{<:Vector, String, UInt128} : Tuple{<:Vector, Nothing, UInt128} @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @@ -687,15 +686,7 @@ precompile_test_harness(false) do dir error("break me") end """) - try - Base.require(Main, :FooBar2) - error("the \"break me\" test failed") - catch exc - isa(exc, LoadError) || rethrow() - exc = exc.error - isa(exc, ErrorException) || rethrow() - "break me" == exc.msg || rethrow() - end + @test_throws Base.Precompilation.PkgPrecompileError Base.require(Main, :FooBar2) # Test that trying to eval into closed modules during precompilation is an error FooBar3_file = joinpath(dir, "FooBar3.jl") @@ -707,14 +698,7 @@ precompile_test_harness(false) do dir $code end """) - try - Base.require(Main, :FooBar3) - catch exc - isa(exc, LoadError) || rethrow() - exc = exc.error - isa(exc, ErrorException) || rethrow() - occursin("Evaluation into the closed module `Base` breaks incremental compilation", exc.msg) || rethrow() - end + @test_throws Base.Precompilation.PkgPrecompileError Base.require(Main, :FooBar3) end # Test transitive dependency for #21266 @@ -2139,7 +2123,7 @@ precompile_test_harness("Test flags") do load_path ji, ofile = Base.compilecache(Base.PkgId("TestFlags"); flags=`--check-bounds=no -O3`) open(ji, "r") do io Base.isvalid_cache_header(io) - _, _, _, _, _, _, _, flags = Base.parse_cache_header(io, ji) + _, _, _, _, _, _, flags = Base.parse_cache_header(io, ji) cacheflags = Base.CacheFlags(flags) @test cacheflags.check_bounds == 2 @test cacheflags.opt_level == 3 @@ -2154,7 +2138,7 @@ if Base.get_bool_env("CI", false) && (Sys.ARCH === :x86_64 || Sys.ARCH === :aarc idx = findfirst(cachefiles) do cf Base.stale_cachefile(pkgpath, cf) !== true end - targets = Base.parse_image_targets(Base.parse_cache_header(cachefiles[idx])[7]) + targets = Base.parse_image_targets(Base.parse_cache_header(cachefiles[idx])[6]) @test length(targets) > 1 end end @@ -2545,4 +2529,724 @@ let io = IOBuffer() @test isempty(String(take!(io))) end +# Test --compiled-modules=strict in precompilepkgs +@testset "compiled-modules=strict with dependencies" begin + mkdepottempdir() do depot + # Create three packages: one that fails to precompile, one that loads it, one that doesn't + project_path = joinpath(depot, "testenv") + mkpath(project_path) + + # Create FailPkg - a package that can't be precompiled + fail_pkg_path = joinpath(depot, "dev", "FailPkg") + mkpath(joinpath(fail_pkg_path, "src")) + write(joinpath(fail_pkg_path, "Project.toml"), + """ + name = "FailPkg" + uuid = "10000000-0000-0000-0000-000000000001" + version = "0.1.0" + """) + write(joinpath(fail_pkg_path, "src", "FailPkg.jl"), + """ + module FailPkg + print("Now FailPkg is running.\n") + error("expected fail") + end + """) + + # Create LoadsFailPkg - depends on and loads FailPkg (should fail with strict) + loads_pkg_path = joinpath(depot, "dev", "LoadsFailPkg") + mkpath(joinpath(loads_pkg_path, "src")) + write(joinpath(loads_pkg_path, "Project.toml"), + """ + name = "LoadsFailPkg" + uuid = "20000000-0000-0000-0000-000000000002" + version = "0.1.0" + + [deps] + FailPkg = "10000000-0000-0000-0000-000000000001" + """) + write(joinpath(loads_pkg_path, "src", "LoadsFailPkg.jl"), + """ + module LoadsFailPkg + print("Now LoadsFailPkg is running.\n") + import FailPkg + print("unreachable\n") + end + """) + + # Create DependsOnly - depends on FailPkg but doesn't load it (should succeed) + depends_pkg_path = joinpath(depot, "dev", "DependsOnly") + mkpath(joinpath(depends_pkg_path, "src")) + write(joinpath(depends_pkg_path, "Project.toml"), + """ + name = "DependsOnly" + uuid = "30000000-0000-0000-0000-000000000003" + version = "0.1.0" + + [deps] + FailPkg = "10000000-0000-0000-0000-000000000001" + """) + write(joinpath(depends_pkg_path, "src", "DependsOnly.jl"), + """ + module DependsOnly + # Has FailPkg as a dependency but doesn't load it + print("Now DependsOnly is running.\n") + end + """) + + # Create main project with all packages + write(joinpath(project_path, "Project.toml"), + """ + [deps] + LoadsFailPkg = "20000000-0000-0000-0000-000000000002" + DependsOnly = "30000000-0000-0000-0000-000000000003" + """) + write(joinpath(project_path, "Manifest.toml"), + """ + julia_version = "1.13.0" + manifest_format = "2.0" + + [[DependsOnly]] + deps = ["FailPkg"] + uuid = "30000000-0000-0000-0000-000000000003" + version = "0.1.0" + + [[FailPkg]] + uuid = "10000000-0000-0000-0000-000000000001" + version = "0.1.0" + + [[LoadsFailPkg]] + deps = ["FailPkg"] + uuid = "20000000-0000-0000-0000-000000000002" + version = "0.1.0" + + [[deps.DependsOnly]] + deps = ["FailPkg"] + path = "../dev/DependsOnly/" + uuid = "30000000-0000-0000-0000-000000000003" + version = "0.1.0" + + [[deps.FailPkg]] + path = "../dev/FailPkg/" + uuid = "10000000-0000-0000-0000-000000000001" + version = "0.1.0" + + [[deps.LoadsFailPkg]] + deps = ["FailPkg"] + path = "../dev/LoadsFailPkg/" + uuid = "20000000-0000-0000-0000-000000000002" + version = "0.1.0" + """) + + # Call precompilepkgs with output redirected to a file + LoadsFailPkg_output = joinpath(depot, "LoadsFailPkg_output.txt") + DependsOnly_output = joinpath(depot, "DependsOnly_output.txt") + original_depot_path = copy(Base.DEPOT_PATH) + old_proj = Base.active_project() + try + push!(empty!(DEPOT_PATH), depot) + Base.set_active_project(project_path) + precompile_capture(file, pkg) = open(file, "w") do io + try + r = Base.Precompilation.precompilepkgs([pkg]; io, fancyprint=true) + @test r isa Vector{String} + r + catch ex + ex isa Base.Precompilation.PkgPrecompileError || rethrow() + ex + end + end + loadsfailpkg = precompile_capture(LoadsFailPkg_output, "LoadsFailPkg") + @test loadsfailpkg isa Base.Precompilation.PkgPrecompileError + dependsonly = precompile_capture(DependsOnly_output, "DependsOnly") + @test length(dependsonly) == 1 + finally + Base.set_active_project(old_proj) + append!(empty!(DEPOT_PATH), original_depot_path) + end + + output = read(LoadsFailPkg_output, String) + # LoadsFailPkg should fail because it tries to load FailPkg with --compiled-modules=strict + @test count("LoadError: expected fail", output) == 1 + @test count("expected fail", output) == 1 + @test count("βœ— FailPkg", output) > 0 + @test count("βœ— LoadsFailPkg", output) > 0 + @test count("Now FailPkg is running.", output) == 1 + @test count("Now LoadsFailPkg is running.", output) == 1 + @test count("DependsOnly precompiling.", output) == 0 + + # DependsOnly should succeed because it doesn't actually load FailPkg + output = read(DependsOnly_output, String) + @test count("LoadError: expected fail", output) == 0 + @test count("expected fail", output) == 0 + @test count("βœ— FailPkg", output) > 0 + @test count("Precompiling DependsOnly finished.", output) == 1 + @test count("Now FailPkg is running.", output) == 0 + @test count("Now DependsOnly is running.", output) == 1 + end +end + +# Issue #61198 - extensions with superset triggers must be in the precompilation dep graph +@testset "precompilation dep graph includes transitively-triggered extensions" begin + mkdepottempdir() do depot + project_path = joinpath(depot, "testenv") + mkpath(project_path) + + parent_uuid = "10000000-0000-0000-0000-000000000023" + triga_uuid = "10000000-0000-0000-0000-000000000050" + trigb_uuid = "20000000-0000-0000-0000-000000000001" + top_uuid = "10000000-0000-0000-0000-000000000064" + + # ParentPkg with two extensions: ExtA triggered by TrigA, + # ExtAB triggered by [TrigA, TrigB] (superset of ExtA's triggers) + parent_dir = joinpath(depot, "dev", "ParentPkg") + mkpath(joinpath(parent_dir, "src")) + mkpath(joinpath(parent_dir, "ext")) + write(joinpath(parent_dir, "Project.toml"), """ + name = "ParentPkg" + uuid = "$parent_uuid" + version = "0.1.0" + + [weakdeps] + TrigA = "$triga_uuid" + TrigB = "$trigb_uuid" + + [extensions] + ExtA = "TrigA" + ExtAB = ["TrigA", "TrigB"] + """) + write(joinpath(parent_dir, "src", "ParentPkg.jl"), """ + module ParentPkg + end + """) + write(joinpath(parent_dir, "ext", "ExtA.jl"), """ + module ExtA + using ParentPkg, TrigA + end + """) + write(joinpath(parent_dir, "ext", "ExtAB.jl"), """ + module ExtAB + using ParentPkg, TrigA, TrigB + end + """) + + triga_dir = joinpath(depot, "dev", "TrigA") + mkpath(joinpath(triga_dir, "src")) + write(joinpath(triga_dir, "Project.toml"), """ + name = "TrigA" + uuid = "$triga_uuid" + version = "0.1.0" + """) + write(joinpath(triga_dir, "src", "TrigA.jl"), """ + module TrigA + end + """) + + trigb_dir = joinpath(depot, "dev", "TrigB") + mkpath(joinpath(trigb_dir, "src")) + write(joinpath(trigb_dir, "Project.toml"), """ + name = "TrigB" + uuid = "$trigb_uuid" + version = "0.1.0" + """) + write(joinpath(trigb_dir, "src", "TrigB.jl"), """ + module TrigB + end + """) + + # TopPkg depends on ParentPkg + both triggers, so both extensions fire + top_dir = joinpath(depot, "dev", "TopPkg") + mkpath(joinpath(top_dir, "src")) + write(joinpath(top_dir, "Project.toml"), """ + name = "TopPkg" + uuid = "$top_uuid" + version = "0.1.0" + + [deps] + ParentPkg = "$parent_uuid" + TrigA = "$triga_uuid" + TrigB = "$trigb_uuid" + """) + write(joinpath(top_dir, "src", "TopPkg.jl"), """ + module TopPkg + using ParentPkg, TrigA, TrigB + end + """) + + write(joinpath(project_path, "Project.toml"), """ + [deps] + ParentPkg = "$parent_uuid" + TrigA = "$triga_uuid" + TrigB = "$trigb_uuid" + TopPkg = "$top_uuid" + """) + + write(joinpath(project_path, "Manifest.toml"), """ + manifest_format = "2.0" + + [[deps.ParentPkg]] + path = "../dev/ParentPkg/" + uuid = "$parent_uuid" + version = "0.1.0" + + [deps.ParentPkg.weakdeps] + TrigA = "$triga_uuid" + TrigB = "$trigb_uuid" + + [deps.ParentPkg.extensions] + ExtA = "TrigA" + ExtAB = ["TrigA", "TrigB"] + + [[deps.TrigA]] + path = "../dev/TrigA/" + uuid = "$triga_uuid" + version = "0.1.0" + + [[deps.TrigB]] + path = "../dev/TrigB/" + uuid = "$trigb_uuid" + version = "0.1.0" + + [[deps.TopPkg]] + deps = ["ParentPkg", "TrigA", "TrigB"] + path = "../dev/TopPkg/" + uuid = "$top_uuid" + version = "0.1.0" + """) + + original_depot_path = copy(Base.DEPOT_PATH) + old_proj = Base.active_project() + try + push!(empty!(DEPOT_PATH), depot) + Base.set_active_project(project_path) + + # First precompilation: compile everything + Base.Precompilation.precompilepkgs(; io=IOBuffer(), fancyprint=false) + + # Second precompilation: should not recompile anything + io = IOBuffer() + Base.Precompilation.precompilepkgs(; io, fancyprint=false) + @test isempty(takestring!(io)) + finally + Base.set_active_project(old_proj) + append!(empty!(DEPOT_PATH), original_depot_path) + end + end +end + +precompile_test_harness("invalidation for 'foreign-keyed' Preferences") do load_path + # Test that compile-time preferences invalidate, even when queried from a + # "foreign" UUID / package namespace + foreign_uuid_str = "b5f1a95c-d45e-4b9e-84c6-b7ea3b5e5f22" + foreign_uuid = Base.UUID(foreign_uuid_str) + + # Package that records compile-time preferences for a "foreign" UUID + write(joinpath(load_path, "PkgCrossPrefs.jl"), + """ + module PkgCrossPrefs + # Query preferences for a foreign package UUID (cross-UUID tracking) + Base.record_compiletime_preference(Base.UUID("$foreign_uuid_str"), "debug") + # Also query a preference that won't be set + Base.record_compiletime_preference(Base.UUID("$foreign_uuid_str"), "unset_opt") + end + """) + + env_base_dir = mkpath(joinpath(load_path, "env_base")) + env_changed_dir = mkpath(joinpath(load_path, "env_changed")) + env_unset_dir = mkpath(joinpath(load_path, "env_unset_set")) + + env_base = joinpath(env_base_dir, "Project.toml") + env_changed = joinpath(env_changed_dir, "Project.toml") + env_unset_set = joinpath(env_unset_dir, "Project.toml") + + write(env_base, """ + [deps] + PkgForeign = "$foreign_uuid_str" + + [preferences.PkgForeign] + debug = false + """) + write(env_changed, """ + [deps] + PkgForeign = "$foreign_uuid_str" + + [preferences.PkgForeign] + debug = true + """) + write(env_unset_set, """ + [deps] + PkgForeign = "$foreign_uuid_str" + + [preferences.PkgForeign] + debug = false + unset_opt = "now_set" + """) + + old_proj = Base.active_project() + try + Base.set_active_project(env_base) + cachefile, _ = Base.compilecache(Base.PkgId("PkgCrossPrefs")) + pkg_file = joinpath(load_path, "PkgCrossPrefs.jl") + + # Cache is not stale with the original preferences + @test Base.stale_cachefile(pkg_file, cachefile) !== true + # Changing the (foreign) preferences makes the cache stale + Base.set_active_project(env_changed) + @test Base.stale_cachefile(pkg_file, cachefile) === true + # Restoring the original preferences makes it not stale again + Base.set_active_project(env_base) + @test Base.stale_cachefile(pkg_file, cachefile) !== true + # Setting a previously-unset preference also causes staleness + Base.set_active_project(env_unset_set) + @test Base.stale_cachefile(pkg_file, cachefile) === true + finally + Base.set_active_project(old_proj) + end +end + +precompile_test_harness("Preferences hash collision (issue #59344), part 1") do load_path + # Test for the preference hash collision bug (https://github.com/JuliaLang/julia/issues/59344) + # related to skipping unset preferences in preference hash + pkg_uuid_str = "c9de8a70-0fad-4996-b3e2-f9c45bce31af" + pkg_uuid = Base.UUID(pkg_uuid_str) + + pkg_src_dir = mkpath(joinpath(load_path, "PkgHashCollision", "src")) + write(joinpath(pkg_src_dir, "PkgHashCollision.jl"), + """ + module PkgHashCollision + Base.record_compiletime_preference(Base.UUID("$pkg_uuid_str"), "xyz") + Base.record_compiletime_preference(Base.UUID("$pkg_uuid_str"), "abc") + end + """) + write(joinpath(load_path, "PkgHashCollision", "Project.toml"), """ + name = "PkgHashCollision" + uuid = "$pkg_uuid_str" + """) + + env_xyz_dir = mkpath(joinpath(load_path, "env_xyz")) + env_abc_dir = mkpath(joinpath(load_path, "env_abc")) + + env_xyz = joinpath(env_xyz_dir, "Project.toml") + env_abc = joinpath(env_abc_dir, "Project.toml") + + # First config: xyz has the value, abc is unset + write(env_xyz, """ + [deps] + PkgHashCollision = "$pkg_uuid_str" + + [preferences.PkgHashCollision] + xyz = "same_value" + """) + # Second config: abc has the same value, xyz is unset + write(env_abc, """ + [deps] + PkgHashCollision = "$pkg_uuid_str" + + [preferences.PkgHashCollision] + abc = "same_value" + """) + + old_proj = Base.active_project() + try + Base.set_active_project(env_xyz) + cachefile, _ = Base.compilecache(Base.PkgId(pkg_uuid, "PkgHashCollision")) + pkg_file = joinpath(load_path, "PkgHashCollision", "src", "PkgHashCollision.jl") + + @test Base.stale_cachefile(pkg_file, cachefile) !== true + Base.set_active_project(env_abc) + @test Base.stale_cachefile(pkg_file, cachefile) === true + finally + Base.set_active_project(old_proj) + end +end + +precompile_test_harness("Preferences hash collision (issue #59344), part 2") do load_path + # On Julia β‰₯1.13, `pref = false` and an unset preference triggered a hash collision, + # causing preference changes not to invalidate. Test that this invalidates as expected. + pkg_uuid_str = "d2e8c371-1b3f-4f5a-8c7e-9a2b1d4e6f8c" + pkg_uuid = Base.UUID(pkg_uuid_str) + + pkg_src_dir = mkpath(joinpath(load_path, "PkgFalsePrefs", "src")) + write(joinpath(pkg_src_dir, "PkgFalsePrefs.jl"), + """ + module PkgFalsePrefs + Base.record_compiletime_preference(Base.UUID("$pkg_uuid_str"), "flag") + end + """) + write(joinpath(load_path, "PkgFalsePrefs", "Project.toml"), """ + name = "PkgFalsePrefs" + uuid = "$pkg_uuid_str" + """) + + env_false_dir = mkpath(joinpath(load_path, "env_false")) + env_none_dir = mkpath(joinpath(load_path, "env_none")) + + env_false = joinpath(env_false_dir, "Project.toml") + env_none = joinpath(env_none_dir, "Project.toml") + + # Compiled with flag = false + write(env_false, """ + [deps] + PkgFalsePrefs = "$pkg_uuid_str" + + [preferences.PkgFalsePrefs] + flag = false + """) + # Preference removed entirely + write(env_none, """ + [deps] + PkgFalsePrefs = "$pkg_uuid_str" + """) + + old_proj = Base.active_project() + try + Base.set_active_project(env_false) + cachefile, _ = Base.compilecache(Base.PkgId(pkg_uuid, "PkgFalsePrefs")) + pkg_file = joinpath(load_path, "PkgFalsePrefs", "src", "PkgFalsePrefs.jl") + + @test Base.stale_cachefile(pkg_file, cachefile) !== true + Base.set_active_project(env_none) + @test Base.stale_cachefile(pkg_file, cachefile) === true + finally + Base.set_active_project(old_proj) + end +end + +# Workspace sub-environment precompilation should not precompile packages from other sub-environments +@testset "workspace sub-environment precompilation scoping" begin + mkdepottempdir() do depot + workspace_path = joinpath(@__DIR__, "project", "Workspaces", "PrecompileExt") + fooenv_path = joinpath(workspace_path, "FooEnv") + + original_depot_path = copy(Base.DEPOT_PATH) + old_proj = Base.active_project() + try + push!(empty!(DEPOT_PATH), depot) + # Activate FooEnv (only depends on Foo, not Bar) + Base.set_active_project(fooenv_path) + + io = IOBuffer() + ioc = IOContext(io, :color => false) + Base.Precompilation.precompilepkgs(; io=ioc, fancyprint=false) + output = String(take!(io)) + + # Foo should be precompiled + @test occursin("Foo", output) + # Bar should NOT be precompiled (it's in another sub-environment) + @test !occursin("Bar", output) + finally + Base.set_active_project(old_proj) + append!(empty!(DEPOT_PATH), original_depot_path) + end + end +end + +# Test that warn_loaded names loaded packages and counts affected dependents +@testset "warn_loaded names packages and counts dependents" begin + mkdepottempdir() do depot; mktempdir() do dir + # Create LoadedDep old source β€” loaded at the start of the script + loaded_dep_old_path = joinpath(dir, "dev", "LoadedDepOld") + mkpath(joinpath(loaded_dep_old_path, "src")) + write(joinpath(loaded_dep_old_path, "Project.toml"), + """ + name = "LoadedDep" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.1.0" + """) + write(joinpath(loaded_dep_old_path, "src", "LoadedDep.jl"), + """ + module LoadedDep + const _v = 1 # old version marker forces a different build_id + end + """) + + # Create LoadedDep new source β€” resolved by the environment after switching + loaded_dep_new_path = joinpath(dir, "dev", "LoadedDepNew") + mkpath(joinpath(loaded_dep_new_path, "src")) + write(joinpath(loaded_dep_new_path, "Project.toml"), + """ + name = "LoadedDep" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.2.0" + """) + write(joinpath(loaded_dep_new_path, "src", "LoadedDep.jl"), + """ + module LoadedDep + const _v = 2 # new version marker forces a different build_id + end + """) + + # Create DepUser β€” depends on LoadedDep + depuser_path = joinpath(dir, "dev", "DepUser") + mkpath(joinpath(depuser_path, "src")) + write(joinpath(depuser_path, "Project.toml"), + """ + name = "DepUser" + uuid = "b2b2b2b2-0000-0000-0000-000000000002" + version = "0.1.0" + + [deps] + LoadedDep = "a1a1a1a1-0000-0000-0000-000000000001" + """) + write(joinpath(depuser_path, "src", "DepUser.jl"), + """ + module DepUser + import LoadedDep + end + """) + + # old_project: used to load the old version of LoadedDep + old_project_path = joinpath(dir, "old_project") + mkpath(old_project_path) + write(joinpath(old_project_path, "Project.toml"), + """ + [deps] + LoadedDep = "a1a1a1a1-0000-0000-0000-000000000001" + """) + write(joinpath(old_project_path, "Manifest.toml"), + """ + manifest_format = "2.0" + + [[deps.LoadedDep]] + path = "../dev/LoadedDepOld/" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.1.0" + """) + + # new_project: resolved after loading old version; has LoadedDep new source + new_project_path = joinpath(dir, "new_project") + mkpath(new_project_path) + write(joinpath(new_project_path, "Project.toml"), + """ + [deps] + DepUser = "b2b2b2b2-0000-0000-0000-000000000002" + LoadedDep = "a1a1a1a1-0000-0000-0000-000000000001" + """) + write(joinpath(new_project_path, "Manifest.toml"), + """ + manifest_format = "2.0" + + [[deps.DepUser]] + deps = ["LoadedDep"] + path = "../dev/DepUser/" + uuid = "b2b2b2b2-0000-0000-0000-000000000002" + version = "0.1.0" + + [[deps.LoadedDep]] + path = "../dev/LoadedDepNew/" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.2.0" + """) + + # Load the old LoadedDep first, then switch to the new project whose manifest + # resolves a different source for LoadedDep β€” this causes a build_id mismatch + # and should trigger the warn_loaded warning. + script = """ + using LoadedDep + Base.set_active_project($(repr(new_project_path))) + Base.Precompilation.precompilepkgs(; fancyprint=false, warn_loaded=true) + """ + + cmd = addenv(`$(Base.julia_cmd()) --startup-file=no --project=$(old_project_path) -e $script`, + "JULIA_DEPOT_PATH" => depot) + + out = Base.PipeEndpoint() + log = @async read(out, String) + try + proc = run(pipeline(cmd, stdout=out, stderr=out)) + @test success(proc) + catch + @show fetch(log) + rethrow() + end + output = fetch(log) + @test occursin("currently loaded", output) + @test occursin("LoadedDep", output) + end end +end + +# Test that warn_loaded does not warn when the loaded dep is already at the correct version +@testset "warn_loaded does not warn when loaded dep matches env version" begin + mkdepottempdir() do depot; mktempdir() do dir + loaded_dep_path = joinpath(dir, "dev", "LoadedDep") + mkpath(joinpath(loaded_dep_path, "src")) + write(joinpath(loaded_dep_path, "Project.toml"), + """ + name = "LoadedDep" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.1.0" + """) + write(joinpath(loaded_dep_path, "src", "LoadedDep.jl"), + """ + module LoadedDep + end + """) + + depuser_path = joinpath(dir, "dev", "DepUser") + mkpath(joinpath(depuser_path, "src")) + write(joinpath(depuser_path, "Project.toml"), + """ + name = "DepUser" + uuid = "b2b2b2b2-0000-0000-0000-000000000002" + version = "0.1.0" + + [deps] + LoadedDep = "a1a1a1a1-0000-0000-0000-000000000001" + """) + write(joinpath(depuser_path, "src", "DepUser.jl"), + """ + module DepUser + import LoadedDep + end + """) + + project_path = joinpath(dir, "project") + mkpath(project_path) + write(joinpath(project_path, "Project.toml"), + """ + [deps] + DepUser = "b2b2b2b2-0000-0000-0000-000000000002" + LoadedDep = "a1a1a1a1-0000-0000-0000-000000000001" + """) + # Manifest points to the same source as what gets loaded β€” versions match + write(joinpath(project_path, "Manifest.toml"), + """ + manifest_format = "2.0" + + [[deps.DepUser]] + deps = ["LoadedDep"] + path = "../dev/DepUser/" + uuid = "b2b2b2b2-0000-0000-0000-000000000002" + version = "0.1.0" + + [[deps.LoadedDep]] + path = "../dev/LoadedDep/" + uuid = "a1a1a1a1-0000-0000-0000-000000000001" + version = "0.1.0" + """) + + # Load LoadedDep first (caching it), then run precompilepkgs β€” the env resolves the + # same version that is loaded, so no version-mismatch warning should appear. + script = """ + using LoadedDep + Base.Precompilation.precompilepkgs(; fancyprint=false, warn_loaded=true) + """ + + cmd = addenv(`$(Base.julia_cmd()) --startup-file=no --project=$(project_path) -e $script`, + "JULIA_DEPOT_PATH" => depot) + + out = Base.PipeEndpoint() + log = @async read(out, String) + try + proc = run(pipeline(cmd, stdout=out, stderr=out)) + @test success(proc) + catch + @show fetch(log) + rethrow() + end + output = fetch(log) + @test !occursin("currently loaded", output) + end end +end + finish_precompile_test!() diff --git a/test/project/Workspaces/PrecompileExt/Bar/Project.toml b/test/project/Workspaces/PrecompileExt/Bar/Project.toml new file mode 100644 index 0000000000000..2cb21380e2cb9 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Bar/Project.toml @@ -0,0 +1,3 @@ +name = "Bar" +uuid = "ab000000-0000-0000-0000-000000000002" +version = "0.1.0" diff --git a/test/project/Workspaces/PrecompileExt/Bar/src/Bar.jl b/test/project/Workspaces/PrecompileExt/Bar/src/Bar.jl new file mode 100644 index 0000000000000..39d7fb696695f --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Bar/src/Bar.jl @@ -0,0 +1,3 @@ +module Bar +greet() = "hello from Bar" +end diff --git a/test/project/Workspaces/PrecompileExt/BarEnv/Project.toml b/test/project/Workspaces/PrecompileExt/BarEnv/Project.toml new file mode 100644 index 0000000000000..ef3559abfcccc --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/BarEnv/Project.toml @@ -0,0 +1,3 @@ +[deps] +Foo = "ab000000-0000-0000-0000-000000000001" +Bar = "ab000000-0000-0000-0000-000000000002" diff --git a/test/project/Workspaces/PrecompileExt/Foo/Project.toml b/test/project/Workspaces/PrecompileExt/Foo/Project.toml new file mode 100644 index 0000000000000..7a085f8e1ba01 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Foo/Project.toml @@ -0,0 +1,9 @@ +name = "Foo" +uuid = "ab000000-0000-0000-0000-000000000001" +version = "0.1.0" + +[weakdeps] +Bar = "ab000000-0000-0000-0000-000000000002" + +[extensions] +FooBarExt = "Bar" diff --git a/test/project/Workspaces/PrecompileExt/Foo/ext/FooBarExt.jl b/test/project/Workspaces/PrecompileExt/Foo/ext/FooBarExt.jl new file mode 100644 index 0000000000000..b4d48becd44ee --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Foo/ext/FooBarExt.jl @@ -0,0 +1,3 @@ +module FooBarExt +using Foo, Bar +end diff --git a/test/project/Workspaces/PrecompileExt/Foo/src/Foo.jl b/test/project/Workspaces/PrecompileExt/Foo/src/Foo.jl new file mode 100644 index 0000000000000..b094f86815cd8 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Foo/src/Foo.jl @@ -0,0 +1,3 @@ +module Foo +greet() = "hello from Foo" +end diff --git a/test/project/Workspaces/PrecompileExt/FooEnv/Project.toml b/test/project/Workspaces/PrecompileExt/FooEnv/Project.toml new file mode 100644 index 0000000000000..646b29605cbd7 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/FooEnv/Project.toml @@ -0,0 +1,2 @@ +[deps] +Foo = "ab000000-0000-0000-0000-000000000001" diff --git a/test/project/Workspaces/PrecompileExt/Manifest.toml b/test/project/Workspaces/PrecompileExt/Manifest.toml new file mode 100644 index 0000000000000..685fe923a8cd4 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Manifest.toml @@ -0,0 +1,25 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.14.0-DEV" +manifest_format = "2.1" +project_hash = "491b0496f036790ab9001fcf6b38ae497256ad30" + +[[deps.Bar]] +path = "Bar" +uuid = "ab000000-0000-0000-0000-000000000002" +version = "0.1.0" + + [deps.Bar.syntax] + julia_version = "1.14.0-DEV.1886" + +[[deps.Foo]] +path = "Foo" +uuid = "ab000000-0000-0000-0000-000000000001" +version = "0.1.0" +weakdeps = ["Bar"] + + [deps.Foo.extensions] + FooBarExt = "Bar" + + [deps.Foo.syntax] + julia_version = "1.14.0-DEV.1886" diff --git a/test/project/Workspaces/PrecompileExt/Project.toml b/test/project/Workspaces/PrecompileExt/Project.toml new file mode 100644 index 0000000000000..2361f324d6430 --- /dev/null +++ b/test/project/Workspaces/PrecompileExt/Project.toml @@ -0,0 +1,2 @@ +[workspace] +projects = ["FooEnv", "BarEnv"] diff --git a/test/subtype.jl b/test/subtype.jl index ee7864da1dd79..3fba7e920c738 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2793,3 +2793,28 @@ end #issue 58115 @test Tuple{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{ Union{Tuple{}, Tuple{Tuple{}}}}}}}}}}}}} , Tuple{}} <: Tuple{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Union{Tuple{}, Tuple{Tuple{}}}}}}}}}}}}}}}, Tuple{}} + +# JETLS#509 β€” inference hang with Union-bounded parametric types +# subtype_ccheck leaked right-side Union choices into the shared +# Runions statestack, causing exponential state iteration when +# multiple Union-bounded parameters share a common variable. +struct JETLS509S{F, A<:Union{Ref{F},Val{F}}, B<:Union{Ref{F},Val{F}}, + C<:Union{Ref{F},Val{F}}, D<:Union{Ref{F},Val{F}}, + E<:Union{Ref{F},Val{F}}, G<:Union{Ref{F},Val{F}}} +end +JETLS509f(a::JETLS509S{F}, b::JETLS509S{F}, s::Union{Nothing,Ref{F}}=nothing) where {F} = 1 +let tt = Tuple{typeof(JETLS509f), + JETLS509S{F,A,B,C,D,E,G} where { + A<:Union{Ref{F},Val{F}}, B<:Union{Ref{F},Val{F}}, + C<:Union{Ref{F},Val{F}}, D<:Union{Ref{F},Val{F}}, + E<:Union{Ref{F},Val{F}}, G<:Union{Ref{F},Val{F}}}, + JETLS509S{F,A,B,C,D,E,G} where { + A<:Union{Ref{F},Val{F}}, B<:Union{Ref{F},Val{F}}, + C<:Union{Ref{F},Val{F}}, D<:Union{Ref{F},Val{F}}, + E<:Union{Ref{F},Val{F}}, G<:Union{Ref{F},Val{F}}}, + } where F + @test Base.code_typed_by_type(tt) isa Vector +end +@test !(Tuple{Union{Int16,Int8},Ref{Int16},Ref{Int16}} <: Tuple{<:Union{S,T},Ref{S},Ref{T}} where {S,T}) +@test !(Tuple{Ref{Int16},Ref{Int16},Union{Int16,Int8}} <: Tuple{Ref{S},Ref{T},<:Union{S,T}} where {S,T}) +@test Tuple{NTuple{2,Int}, Int8} <: Tuple{<:Union{NTuple{2,T},Tuple{S,T}}, <:T} where {S,T>:Int} diff --git a/test/trimming/trimmability.jl b/test/trimming/trimmability.jl index 0fd9eedc6999d..6e0b4d4c0683d 100644 --- a/test/trimming/trimmability.jl +++ b/test/trimming/trimmability.jl @@ -26,6 +26,70 @@ function add_one(x::Cint)::Cint return x + 1 end +function _test_cat() + # hcat + _cat1a = hcat(randn(3), rand(3), randn(3)) + _cat1b = [randn(3) rand(3) randn(3)] + _cat1c = hcat(randn(3,3), rand(3,3), randn(3,3)) + _cat1d = [randn(3,3) rand(3,3) randn(3,3)] + _cat1e = hcat(randn(3,3,3), rand(3,3,3), randn(3,3,3)) + _cat1f = [randn(3,3,3) rand(3,3,3) randn(3,3,3)] + + # v_cat + _cat2a = vcat(randn(3), rand(3), randn(3)) + _cat2b = [randn(3); rand(3); randn(3)] + _cat2c = vcat(randn(3,3), rand(3,3), randn(3,3)) + _cat2d = [randn(3,3); rand(3,3); randn(3,3)] + _cat2e = vcat(randn(3,3,3), rand(3,3,3), randn(3,3,3)) + _cat2f = [randn(3,3,3); rand(3,3,3); randn(3,3,3)] + + # hvcat + _cat3a = hvcat((2,2), rand(3,2), randn(3,4), rand(1,2), randn(1,4)) + _cat3b = [rand(3,2) randn(3,4); rand(1,2) randn(1,4)] + _cat3c = hvcat((2, 2), rand(5,2,3), rand(5,4,3), rand(1,2,3), rand(1,4,3)) + _cat3d = [rand(5,2,3) rand(5,4,3); rand(1,2,3) rand(1,4,3)] + + # cat + _cat4a = cat(randn(3), randn(3); dims = 1) + _cat4b = cat(randn(3,3,3), randn(3,3,3); dims = 2) + _cat4c = cat(randn(3), randn(3,3); dims = 2) + _cat4d = cat(randn(3), randn(3), rand(3), rand(3), randn(3), randn(3); dims = (1,)) + _cat4e = cat(randn(3,3), randn(3,3), rand(3,3), rand(3,3), randn(3,3), randn(3,3); dims = (1,2)) + _cat4f = cat(randn(3,3,3), randn(3,3); dims=(1,3)) + + # hvncat + _cat5a = hvncat(2, randn(3), randn(3), randn(3)) + _cat5b = [randn(3) ;; randn(3) ;; randn(3)] + _cat5c = hvncat(2, randn(3,3), randn(3,3), randn(3,3)) + _cat5d = [randn(3,3) ;; randn(3,3) ;; randn(3,3)] + _cat5e = hvncat((1, 2, 2), false, randn(2,3), randn(2,3), randn(2,3), randn(2,3)) + _cat5f = [randn(2,3) ;; randn(2,3) ;;; randn(2,3) ;; randn(2,3)] + + # stack + _cat6a = stack([randn(3), randn(3), randn(3)]) + _cat6b = stack([randn(3), randn(3), randn(3)]; dims=1) + _cat6c = stack([randn(2,3), randn(2,3)]; dims=3) + _cat6d = stack(x -> x .^ 2, [randn(3), randn(3)]) + + # repeat + _cat7a = repeat(randn(3), 2) + _cat7b = repeat(randn(2,3), 2, 3) + _cat7c = repeat(randn(2,3); inner=(2,1), outer=(1,3)) + _cat7d = repeat(randn(3,3,3), 1, 2, 1) + + # aggregate to prevent deletion + _cat1 = _cat1a[1] + _cat1b[1] + _cat1c[1] + _cat1d[1] + _cat1e[1] + _cat1f[1] + _cat2 = _cat2a[1] + _cat2b[1] + _cat2c[1] + _cat2d[1] + _cat2e[1] + _cat2f[1] + _cat3 = _cat3a[1] + _cat3b[1] + _cat3c[1] + _cat3d[1] + _cat4 = _cat4a[1] + _cat4b[1] + _cat4c[1] + _cat4d[1] + _cat4e[1] + _cat4f[1] + _cat5 = _cat5a[1] + _cat5b[1] + _cat5c[1] + _cat5d[1] + _cat5e[1] + _cat5f[1] + _cat6 = _cat6a[1] + _cat6b[1] + _cat6c[1] + _cat6d[1] + _cat7 = _cat7a[1] + _cat7b[1] + _cat7c[1] + _cat7d[1] + + return _cat1 + _cat2 + _cat3 + _cat4 + _cat5 + _cat6 + _cat7 +end + + function @main(args::Vector{String})::Cint println(Core.stdout, str()) println(Core.stdout, PROGRAM_FILE) @@ -45,6 +109,8 @@ function @main(args::Vector{String})::Cint d = mapreduce(x -> x^2, +, sorted_arr) # e = reduce(xor, rand(Int, 10)) + println(Core.stdout, _test_cat()) + for i = 1:10 # https://github.com/JuliaLang/julia/issues/60846 add_one(Cint(i)) diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 7e15a4d196a0c..176f51106c858 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -15,7 +15,13 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test filesize(hello_exe) < 1_900_000 trimmability_exe = joinpath(bindir, "trimmability" * exe_suffix) - @test readchomp(`$trimmability_exe arg1 arg2`) == "Hello, world!\n$trimmability_exe\narg1\narg2\n$(4.0+pi)" + lines = split(readchomp(`$trimmability_exe arg1 arg2`), "\n") + @test lines[1] == "Hello, world!" + @test lines[2] == trimmability_exe + @test lines[3] == "arg1" + @test lines[4] == "arg2" + @test lines[5] == string(4.0+pi) + @test parse(Float64, lines[6]) isa Float64 basic_jll_exe = joinpath(bindir, "basic_jll" * exe_suffix) lines = split(readchomp(`$basic_jll_exe`), "\n")