diff --git a/Make.inc b/Make.inc index 1163f66c2fdbd..729870fbad860 100644 --- a/Make.inc +++ b/Make.inc @@ -359,6 +359,10 @@ USE_MLIR := 0 # Options to use RegionVectorizer USE_RV := 0 +# Use `ccache` for speeding up recompilation of the C/C++ part of Julia. +# Requires the `ccache` executable to be in the `PATH` environment variable. +USECCACHE := 0 + # Cross-compile #XC_HOST := i686-w64-mingw32 #XC_HOST := x86_64-w64-mingw32 diff --git a/base/Base.jl b/base/Base.jl index 0ec70add2a2c4..6ed423a799e4e 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -115,6 +115,12 @@ time_ns() = ccall(:jl_hrtime, UInt64, ()) start_base_include = time_ns() +# A warning to be interpolated in the docstring of every dangerous mutating function in Base, see PR #50824 +const _DOCS_ALIASING_WARNING = """ +!!! warning + Behavior can be unexpected when any mutated argument shares memory with any other argument. +""" + ## Load essential files and libraries include("essentials.jl") include("ctypes.jl") diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 6f9823a5be5d7..8b8e4a760eb9b 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -905,6 +905,8 @@ If `dst` and `src` are of the same type, `dst == src` should hold after the call. If `dst` and `src` are multidimensional arrays, they must have equal [`axes`](@ref). +$(_DOCS_ALIASING_WARNING) + See also [`copyto!`](@ref). !!! compat "Julia 1.1" @@ -1369,6 +1371,8 @@ _unsafe_ind2sub(sz, i) = (@inline; _ind2sub(sz, i)) Store values from array `X` within some subset of `A` as specified by `inds`. The syntax `A[inds...] = X` is equivalent to `(setindex!(A, X, inds...); X)`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = zeros(2,2); @@ -1587,10 +1591,14 @@ eltypeof(x::AbstractArray) = eltype(x) promote_eltypeof() = error() promote_eltypeof(v1) = eltypeof(v1) promote_eltypeof(v1, vs...) = promote_type(eltypeof(v1), promote_eltypeof(vs...)) +promote_eltypeof(v1::T, vs::T...) where {T} = eltypeof(v1) +promote_eltypeof(v1::AbstractArray{T}, vs::AbstractArray{T}...) where {T} = T promote_eltype() = error() promote_eltype(v1) = eltype(v1) promote_eltype(v1, vs...) = promote_type(eltype(v1), promote_eltype(vs...)) +promote_eltype(v1::T, vs::T...) where {T} = eltype(T) +promote_eltype(v1::AbstractArray{T}, vs::AbstractArray{T}...) where {T} = T #TODO: ERROR CHECK _cat(catdim::Int) = Vector{Any}() @@ -3339,6 +3347,8 @@ end Like [`map`](@ref), but stores the result in `destination` rather than a new collection. `destination` must be at least as large as the smallest collection. +$(_DOCS_ALIASING_WARNING) + See also: [`map`](@ref), [`foreach`](@ref), [`zip`](@ref), [`copyto!`](@ref). # Examples diff --git a/base/abstractset.jl b/base/abstractset.jl index 5d0d65dad2de6..a6b123ceab007 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -65,6 +65,8 @@ const ∪ = union Construct the [`union`](@ref) of passed in sets and overwrite `s` with the result. Maintain order with arrays. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> a = Set([3, 4, 5]); @@ -182,6 +184,8 @@ const ∩ = intersect Intersect all passed in sets and overwrite `s` with the result. Maintain order with arrays. + +$(_DOCS_ALIASING_WARNING) """ function intersect!(s::AbstractSet, itrs...) for x in itrs @@ -218,6 +222,8 @@ setdiff(s) = union(s) Remove from set `s` (in-place) each element of each iterable from `itrs`. Maintain order with arrays. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> a = Set([1, 3, 4, 5]); @@ -272,6 +278,8 @@ symdiff(s) = symdiff!(copy(s)) Construct the symmetric difference of the passed in sets, and overwrite `s` with the result. When `s` is an array, the order is maintained. Note that in this case the multiplicity of elements matters. + +$(_DOCS_ALIASING_WARNING) """ function symdiff!(s::AbstractSet, itrs...) for x in itrs diff --git a/base/accumulate.jl b/base/accumulate.jl index eeb9759e125c7..a2d8a1d368d86 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -42,6 +42,8 @@ end cumsum!(B, A; dims::Integer) Cumulative sum of `A` along the dimension `dims`, storing the result in `B`. See also [`cumsum`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumsum!(B::AbstractArray{T}, A; dims::Integer) where {T} = accumulate!(add_sum, B, A, dims=dims) @@ -150,6 +152,8 @@ cumsum(itr) = accumulate(add_sum, itr) Cumulative product of `A` along the dimension `dims`, storing the result in `B`. See also [`cumprod`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumprod!(B::AbstractArray{T}, A; dims::Integer) where {T} = accumulate!(mul_prod, B, A, dims=dims) @@ -159,6 +163,8 @@ cumprod!(B::AbstractArray{T}, A; dims::Integer) where {T} = Cumulative product of a vector `x`, storing the result in `y`. See also [`cumprod`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumprod!(y::AbstractVector, x::AbstractVector) = cumprod!(y, x, dims=1) @@ -301,6 +307,8 @@ Cumulative operation `op` on `A` along the dimension `dims`, storing the result Providing `dims` is optional for vectors. If the keyword argument `init` is given, its value is used to instantiate the accumulation. +$(_DOCS_ALIASING_WARNING) + See also [`accumulate`](@ref), [`cumsum!`](@ref), [`cumprod!`](@ref). # Examples diff --git a/base/array.jl b/base/array.jl index 7fb3d0501bb7b..535aad691c78d 100644 --- a/base/array.jl +++ b/base/array.jl @@ -322,6 +322,8 @@ source and `do` in the destination (1-indexed). The `unsafe` prefix on this function indicates that no validation is performed to ensure that N is inbounds on either array. Incorrect usage may corrupt or segfault your program, in the same manner as C. + +$(_DOCS_ALIASING_WARNING) """ function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T t1 = @_gc_preserve_begin dest @@ -1781,6 +1783,8 @@ place of the removed items; in this case, `indices` must be a `AbstractUnitRange To insert `replacement` before an index `n` without removing any items, use `splice!(collection, n:n-1, replacement)`. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.5" Prior to Julia 1.5, `indices` must always be a `UnitRange`. @@ -2760,6 +2764,8 @@ Remove the items at all the indices which are not given by `inds`, and return the modified `a`. Items which are kept are shifted to fill the resulting gaps. +$(_DOCS_ALIASING_WARNING) + `inds` must be an iterator of sorted and unique integer indices. See also [`deleteat!`](@ref). diff --git a/base/asyncevent.jl b/base/asyncevent.jl index a26945bbb1105..11551df5d0e52 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -118,14 +118,18 @@ end unsafe_convert(::Type{Ptr{Cvoid}}, t::Timer) = t.handle unsafe_convert(::Type{Ptr{Cvoid}}, async::AsyncCondition) = async.handle +# if this returns true, the object has been signaled +# if this returns false, the object is closed function _trywait(t::Union{Timer, AsyncCondition}) set = t.set if set # full barrier now for AsyncCondition t isa Timer || Core.Intrinsics.atomic_fence(:acquire_release) else - t.isopen || return false - t.handle == C_NULL && return false + if !isopen(t) + close(t) # wait for the close to complete + return false + end iolock_begin() set = t.set if !set @@ -133,7 +137,7 @@ function _trywait(t::Union{Timer, AsyncCondition}) lock(t.cond) try set = t.set - if !set && t.isopen && t.handle != C_NULL + if !set && t.handle != C_NULL # wait for set or handle, but not the isopen flag iolock_end() set = wait(t.cond) unlock(t.cond) @@ -160,10 +164,28 @@ end isopen(t::Union{Timer, AsyncCondition}) = t.isopen && t.handle != C_NULL function close(t::Union{Timer, AsyncCondition}) + t.handle == C_NULL && return # short-circuit path iolock_begin() - if isopen(t) - @atomic :monotonic t.isopen = false - ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + if t.handle != C_NULL + if t.isopen + @atomic :monotonic t.isopen = false + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + end + # implement _trywait here without the auto-reset function, just waiting for the final close signal + preserve_handle(t) + lock(t.cond) + try + while t.handle != C_NULL + iolock_end() + wait(t.cond) + unlock(t.cond) + iolock_begin() + lock(t.cond) + end + finally + unlock(t.cond) + unpreserve_handle(t) + end end iolock_end() nothing @@ -220,7 +242,10 @@ function uv_timercb(handle::Ptr{Cvoid}) @atomic :monotonic t.set = true if ccall(:uv_timer_get_repeat, UInt64, (Ptr{Cvoid},), t) == 0 # timer is stopped now - close(t) + if t.isopen + @atomic :monotonic t.isopen = false + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + end end notify(t.cond, true) finally diff --git a/base/asyncmap.jl b/base/asyncmap.jl index be16ba1b27610..c81afbb7e9115 100644 --- a/base/asyncmap.jl +++ b/base/asyncmap.jl @@ -394,6 +394,8 @@ length(itr::AsyncGenerator) = length(itr.collector.enumerator) Like [`asyncmap`](@ref), but stores output in `results` rather than returning a collection. + +$(_DOCS_ALIASING_WARNING) """ function asyncmap!(f, r, c1, c...; ntasks=0, batch_size=nothing) foreach(identity, AsyncCollector(f, r, c1, c...; ntasks=ntasks, batch_size=batch_size)) diff --git a/base/boot.jl b/base/boot.jl index 78b7daaf47d64..561be564d58f4 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -370,6 +370,8 @@ struct InitError <: WrappedException error end +struct PrecompilableError <: Exception end + String(s::String) = s # no constructor yet const Cvoid = Nothing diff --git a/base/combinatorics.jl b/base/combinatorics.jl index d09a5b6c0ce83..4a46e95113840 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -169,6 +169,8 @@ it is even faster to write into a pre-allocated output array with `u .= @view v[ (Even though `permute!` overwrites `v` in-place, it internally requires some allocation to keep track of which elements have been moved.) +$(_DOCS_ALIASING_WARNING) + See also [`invpermute!`](@ref). # Examples @@ -222,6 +224,8 @@ Note that if you have a pre-allocated output array (e.g. `u = similar(v)`), it is quicker to instead employ `u[p] = v`. (`invpermute!` internally allocates a copy of the data.) +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1, 1, 3, 4]; diff --git a/base/env.jl b/base/env.jl index 077b3a4ed28e5..27594acff6b7f 100644 --- a/base/env.jl +++ b/base/env.jl @@ -3,12 +3,29 @@ if Sys.iswindows() const ERROR_ENVVAR_NOT_FOUND = UInt32(203) + const env_dict = IdDict{String, Vector{Cwchar_t}}() + const env_lock = ReentrantLock() + + function memoized_env_lookup(str::AbstractString) + # Windows environment variables have a different format from Linux / MacOS, and previously + # incurred allocations because we had to convert a String to a Vector{Cwchar_t} each time + # an environment variable was looked up. This function memoizes that lookup process, storing + # the String => Vector{Cwchar_t} pairs in env_dict + var = get(env_dict, str, nothing) + if isnothing(var) + var = @lock env_lock begin + env_dict[str] = cwstring(str) + end + end + var + end + _getenvlen(var::Vector{UInt16}) = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,C_NULL,0) _hasenv(s::Vector{UInt16}) = _getenvlen(s) != 0 || Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND - _hasenv(s::AbstractString) = _hasenv(cwstring(s)) + _hasenv(s::AbstractString) = _hasenv(memoized_env_lookup(s)) function access_env(onError::Function, str::AbstractString) - var = cwstring(str) + var = memoized_env_lookup(str) len = _getenvlen(var) if len == 0 return Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND ? "" : onError(str) @@ -21,7 +38,7 @@ if Sys.iswindows() end function _setenv(svar::AbstractString, sval::AbstractString, overwrite::Bool=true) - var = cwstring(svar) + var = memoized_env_lookup(svar) val = cwstring(sval) if overwrite || !_hasenv(var) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val) @@ -30,7 +47,7 @@ if Sys.iswindows() end function _unsetenv(svar::AbstractString) - var = cwstring(svar) + var = memoized_env_lookup(svar) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL) windowserror(:setenv, ret == 0 && Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND) end diff --git a/base/loading.jl b/base/loading.jl index d748283c7c9d8..bbc14c3f1d591 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1686,7 +1686,7 @@ function include_dependency(path::AbstractString) end # we throw PrecompilableError when a module doesn't want to be precompiled -struct PrecompilableError <: Exception end +import Core: PrecompilableError function show(io::IO, ex::PrecompilableError) print(io, "Declaring __precompile__(false) is not allowed in files that are being precompiled.") end diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 2b4bf54d88826..02f2075919fbd 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -1179,8 +1179,7 @@ circshift!(dest::AbstractArray, src, ::Tuple{}) = copyto!(dest, src) Circularly shift, i.e. rotate, the data in `src`, storing the result in `dest`. `shifts` specifies the amount to shift in each dimension. -The `dest` array must be distinct from the `src` array (they cannot -alias each other). +$(_DOCS_ALIASING_WARNING) See also [`circshift`](@ref). """ @@ -1238,6 +1237,8 @@ their indices; any offset results in a (circular) wraparound. If the arrays have overlapping indices, then on the domain of the overlap `dest` agrees with `src`. +$(_DOCS_ALIASING_WARNING) + See also: [`circshift`](@ref). # Examples diff --git a/base/reducedim.jl b/base/reducedim.jl index c1c58ccdfefed..f5a22940310cf 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -448,6 +448,8 @@ _count(f, A::AbstractArrayOrBroadcasted, dims, init) = mapreduce(_bool(f), add_s Count the number of elements in `A` for which `f` returns `true` over the singleton dimensions of `r`, writing the result into `r` in-place. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.5" inplace `count!` was added in Julia 1.5. @@ -525,8 +527,8 @@ sum(f, A::AbstractArray; dims) sum!(r, A) Sum elements of `A` over the singleton dimensions of `r`, and write results to `r`. -Note that since the sum! function is intended to operate without making any allocations, -the target should not alias with the source. + +$(_DOCS_ALIASING_WARNING) # Examples ```jldoctest @@ -601,6 +603,8 @@ prod(f, A::AbstractArray; dims) Multiply elements of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -678,6 +682,8 @@ maximum(f, A::AbstractArray; dims) Compute the maximum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -755,6 +761,8 @@ minimum(f, A::AbstractArray; dims) Compute the minimum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -820,6 +828,8 @@ extrema(f, A::AbstractArray; dims) Compute the minimum and maximum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.8" This method requires Julia 1.8 or later. @@ -895,6 +905,8 @@ all(::Function, ::AbstractArray; dims) Test whether all values in `A` along the singleton dimensions of `r` are `true`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [true false; true false] @@ -968,6 +980,8 @@ any(::Function, ::AbstractArray; dims) Test whether any values in `A` along the singleton dimensions of `r` are `true`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [true false; true false] @@ -1085,6 +1099,8 @@ end Find the minimum of `A` and the corresponding linear index along singleton dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. `NaN` is treated as less than all other values except `missing`. + +$(_DOCS_ALIASING_WARNING) """ function findmin!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) @@ -1156,6 +1172,8 @@ end Find the maximum of `A` and the corresponding linear index along singleton dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. `NaN` is treated as greater than all other values except `missing`. + +$(_DOCS_ALIASING_WARNING) """ function findmax!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) diff --git a/base/shell.jl b/base/shell.jl index 5bfd11fb46d29..48214418bdee5 100644 --- a/base/shell.jl +++ b/base/shell.jl @@ -18,12 +18,11 @@ end function shell_parse(str::AbstractString, interpolate::Bool=true; special::AbstractString="", filename="none") - s = SubString(str, firstindex(str)) + last_arg = firstindex(str) # N.B.: This is used by REPLCompletions + s = SubString(str, last_arg) s = rstrip_shell(lstrip(s)) - # N.B.: This is used by REPLCompletions - last_parse = 0:-1 - isempty(s) && return interpolate ? (Expr(:tuple,:()),last_parse) : ([],last_parse) + isempty(s) && return interpolate ? (Expr(:tuple,:()), last_arg) : ([], last_arg) in_single_quotes = false in_double_quotes = false @@ -32,6 +31,7 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; arg = [] i = firstindex(s) st = Iterators.Stateful(pairs(s)) + update_last_arg = false # true after spaces or interpolate function push_nonempty!(list, x) if !isa(x,AbstractString) || !isempty(x) @@ -54,6 +54,7 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; for (j, c) in st j, c = j::Int, c::C if !in_single_quotes && !in_double_quotes && isspace(c) + update_last_arg = true i = consume_upto!(arg, s, i, j) append_2to1!(args, arg) while !isempty(st) @@ -77,12 +78,17 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; # use parseatom instead of parse to respect filename (#28188) ex, j = Meta.parseatom(s, stpos, filename=filename) end - last_parse = (stpos:prevind(s, j)) .+ s.offset - push_nonempty!(arg, ex) + last_arg = stpos + s.offset + update_last_arg = true + push!(arg, ex) s = SubString(s, j) Iterators.reset!(st, pairs(s)) i = firstindex(s) else + if update_last_arg + last_arg = i + s.offset + update_last_arg = false + end if !in_double_quotes && c == '\'' in_single_quotes = !in_single_quotes i = consume_upto!(arg, s, i, j) @@ -124,14 +130,14 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; push_nonempty!(arg, s[i:end]) append_2to1!(args, arg) - interpolate || return args, last_parse + interpolate || return args, last_arg # construct an expression ex = Expr(:tuple) for arg in args push!(ex.args, Expr(:tuple, arg...)) end - return ex, last_parse + return ex, last_arg end function shell_split(s::AbstractString) @@ -216,7 +222,7 @@ function print_shell_escaped_posixly(io::IO, args::AbstractString...) function isword(c::AbstractChar) if '0' <= c <= '9' || 'a' <= c <= 'z' || 'A' <= c <= 'Z' # word characters - elseif c == '_' || c == '/' || c == '+' || c == '-' + elseif c == '_' || c == '/' || c == '+' || c == '-' || c == '.' # other common characters elseif c == '\'' have_single = true diff --git a/base/sort.jl b/base/sort.jl index abf0b9ed07682..971a68fa3d725 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,8 +5,7 @@ module Sort using Base.Order using Base: copymutable, midpoint, require_one_based_indexing, uinttype, - sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit, - IteratorSize, HasShape, IsInfinite, tail + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit import Base: sort, @@ -1470,11 +1469,6 @@ end Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified. -Uses `Base.copymutable` to support immutable collections and iterables. - -!!! compat "Julia 1.10" - `sort` of arbitrary iterables requires at least Julia 1.10. - # Examples ```jldoctest julia> v = [3, 1, 2]; @@ -1492,39 +1486,7 @@ julia> v 2 ``` """ -function sort(v; kws...) - size = IteratorSize(v) - size == HasShape{0}() && throw(ArgumentError("$v cannot be sorted")) - size == IsInfinite() && throw(ArgumentError("infinite iterator $v cannot be sorted")) - sort!(copymutable(v); kws...) -end -sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) # for method disambiguation -sort(::AbstractString; kws...) = - throw(ArgumentError("sort(::AbstractString) is not supported")) -sort(::Tuple; kws...) = - throw(ArgumentError("sort(::Tuple) is only supported for NTuples")) - -function sort(x::NTuple{N}; lt::Function=isless, by::Function=identity, - rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where N - o = ord(lt,by,rev,order) - if N > 9 - v = sort!(copymutable(x), DEFAULT_STABLE, o) - tuple((v[i] for i in 1:N)...) - else - _sort(x, o) - end -end -_sort(x::Union{NTuple{0}, NTuple{1}}, o::Ordering) = x -function _sort(x::NTuple, o::Ordering) - a, b = Base.IteratorsMD.split(x, Val(length(x)>>1)) - merge(_sort(a, o), _sort(b, o), o) -end -merge(x::NTuple, y::NTuple{0}, o::Ordering) = x -merge(x::NTuple{0}, y::NTuple, o::Ordering) = y -merge(x::NTuple{0}, y::NTuple{0}, o::Ordering) = x # Method ambiguity -merge(x::NTuple, y::NTuple, o::Ordering) = - (lt(o, y[1], x[1]) ? (y[1], merge(x, tail(y), o)...) : (x[1], merge(tail(x), y, o)...)) - +sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) ## partialsortperm: the permutation to sort the first k elements of an array ## @@ -1583,6 +1545,8 @@ v[ix[k]] == partialsort(v, k) The return value is the `k`th element of `ix` if `k` is an integer, or view into `ix` if `k` is a range. +$(Base._DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> v = [3, 1, 2, 1]; @@ -1707,6 +1671,8 @@ end Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. `ix` is initialized to contain the values `LinearIndices(A)`. +$(Base._DOCS_ALIASING_WARNING) + !!! compat "Julia 1.9" The method accepting `dims` requires at least Julia 1.9. diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index 17c5d66c160b6..2e04633b87487 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -179,6 +179,7 @@ const _julia_charmap = Dict{UInt32,UInt32}( 0x00B7 => 0x22C5, 0x0387 => 0x22C5, 0x2212 => 0x002D, + 0x210F => 0x0127, ) utf8proc_map(s::AbstractString, flags::Integer, chartransform=identity) = utf8proc_map(String(s), flags, chartransform) diff --git a/base/util.jl b/base/util.jl index 3ccdd0a37ae68..2617d5a602850 100644 --- a/base/util.jl +++ b/base/util.jl @@ -144,7 +144,7 @@ See also [`print`](@ref), [`println`](@ref), [`show`](@ref). printstyled(stdout, msg...; bold=bold, italic=italic, underline=underline, blink=blink, reverse=reverse, hidden=hidden, color=color) """ - Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target) + Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Union{Nothing,String}=nothing) Return a julia command similar to the one of the running process. Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`, @@ -154,6 +154,8 @@ command line arguments that are not at their default values. Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notably not propagated currently. +Unless set to `nothing`, the `cpu_target` keyword argument can be used to override the CPU target set for the running process. + To get the julia command without propagated command line arguments, `julia_cmd()[1]` can be used. !!! compat "Julia 1.1" @@ -246,7 +248,7 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Unio # If pkgimage is set, malloc_log and code_coverage should not @assert opts.malloc_log == 0 && opts.code_coverage == 0 end - return `$julia -C$cpu_target -J$image_file $addflags` + return `$julia -C $cpu_target -J$image_file $addflags` end function julia_exename() diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version index e726eea3656fc..7f124715024ce 100644 --- a/deps/JuliaSyntax.version +++ b/deps/JuliaSyntax.version @@ -1,4 +1,4 @@ JULIASYNTAX_BRANCH = main -JULIASYNTAX_SHA1 = 045d156c44dbb87769c7416d049a7c08908539d4 +JULIASYNTAX_SHA1 = 4f1731d6ce7c2465fc21ea245110b7a39f34658a JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 new file mode 100644 index 0000000000000..c2663955ec773 --- /dev/null +++ b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 @@ -0,0 +1 @@ +8c9d9579eeab1ba40f978a32c9db9900 diff --git a/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 new file mode 100644 index 0000000000000..46647cb3e432b --- /dev/null +++ b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 @@ -0,0 +1 @@ +1bdad624f61482b55deba8727fea1c087bfaea9e1f8afa3b44b984441fb7e663dac067baa4a96ae2d4cbd4a46ae8c87e9d20d2dfcd17046ad194711304184e57 diff --git a/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/md5 b/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/md5 new file mode 100644 index 0000000000000..985e7ef06f2a5 --- /dev/null +++ b/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/md5 @@ -0,0 +1 @@ +769c5379f1ded5529d82d83e092e954c diff --git a/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/sha512 b/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/sha512 new file mode 100644 index 0000000000000..d5f705de6d9d9 --- /dev/null +++ b/deps/checksums/Pkg-563a3387acddad6c427b2f675aa70531c4324cc2.tar.gz/sha512 @@ -0,0 +1 @@ +585760a1c104391bb08f920b6944b88762e942388fd6ca6bf3bfa6a0bf050f99d304c21165a3b82c96dea5a6711af3ddf0cf9b83bf5fd5f015b26f412b24015a diff --git a/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/md5 b/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/md5 deleted file mode 100644 index bddd4fc615336..0000000000000 --- a/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -a68258944ba3c7b8a31864ad5dc6e320 diff --git a/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/sha512 b/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/sha512 deleted file mode 100644 index 0f89a4347a7d6..0000000000000 --- a/deps/checksums/Pkg-e63c601bd3a00a8e6062d926673337c8a659542d.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -e7ea74323f873e6ff0b92b19d368a16df768f21474222ef38918d36d827c92a5597e7fa243e9ef985b02f3bcf65d41904cfeeae9aca64612fdfbb9483c3cbd12 diff --git a/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/md5 b/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/md5 new file mode 100644 index 0000000000000..dbb5746626e3e --- /dev/null +++ b/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/md5 @@ -0,0 +1 @@ +ab41f62c0b16f5464c548e648f05cb94 diff --git a/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/sha512 b/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/sha512 new file mode 100644 index 0000000000000..1c67c8110b375 --- /dev/null +++ b/deps/checksums/SparseArrays-279b363ca8d3129d4742903d37c8b11545fa08a2.tar.gz/sha512 @@ -0,0 +1 @@ +9fa813d057415c3eba1941dd15634a5d4798015b6b72e7524112aeb57522ec54aee653fb1531574441fea08cd472c76f6964e1a0d75fa48a116000196a061d17 diff --git a/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/md5 b/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/md5 deleted file mode 100644 index afeeed49f2e21..0000000000000 --- a/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -85c5dcb49f11bce2d6bb75ef620ea892 diff --git a/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/sha512 b/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/sha512 deleted file mode 100644 index 7cab3aeec5ca9..0000000000000 --- a/deps/checksums/SparseArrays-fa6269b0dc5660cbaddb9365c7d89b206fc55992.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -a1810991895650263c33c3e9c2b3089c66948b86b4de16c4c06ed49841cf5500814641576a6b735d5cf3841991684e908c4828a56ee218be216424e6aa9d5f43 diff --git a/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/md5 b/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/md5 deleted file mode 100644 index 42034e9a42a7b..0000000000000 --- a/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -735ae885122f81261ecf152853ce0b42 diff --git a/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/sha512 b/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/sha512 deleted file mode 100644 index dbec046cef091..0000000000000 --- a/deps/checksums/Statistics-08562cb8abbe67829e437aa8533994121c350c05.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -42dd950bd59fa91540806f9c1b26c391373ef4508106e59f875ca256805951784fe4469b834978a6d20ab340eb979da97b0f4b32c8f53626b03d7b3f84e4cb5a diff --git a/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/md5 b/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/md5 new file mode 100644 index 0000000000000..406ed9e1d1af7 --- /dev/null +++ b/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/md5 @@ -0,0 +1 @@ +30cb9242a167ec2ed652d2923d1f7d23 diff --git a/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/sha512 b/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/sha512 new file mode 100644 index 0000000000000..16b0e72537968 --- /dev/null +++ b/deps/checksums/Statistics-d147f9253aa0f2f71be9c3fed8d51c2215410408.tar.gz/sha512 @@ -0,0 +1 @@ +c325f09ed306bef6d4fe186aa4ce9300f1e400752f776b23220d828468f8bae16a9058683d04176bd32a47fdc32aa71cb6a479a1bfa3102944838658581c94d7 diff --git a/doc/make.jl b/doc/make.jl index a9343a3133a63..6bbc94b925b60 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -154,6 +154,7 @@ DevDocs = [ "devdocs/EscapeAnalysis.md", "devdocs/gc-sa.md", "devdocs/gc.md", + "devdocs/precompile_hang.md", ], "Developing/debugging Julia's C code" => [ "devdocs/backtraces.md", diff --git a/doc/src/devdocs/build/build.md b/doc/src/devdocs/build/build.md index ad3871c2e70f0..c915c669f2c5e 100644 --- a/doc/src/devdocs/build/build.md +++ b/doc/src/devdocs/build/build.md @@ -60,6 +60,16 @@ To run julia from anywhere you can: - write `prefix=/path/to/install/folder` into `Make.user` and then run `make install`. If there is a version of Julia already installed in this folder, you should delete it before running `make install`. +Some of the options you can set to control the build of Julia are listed and documented at the beginning of the file `Make.inc`, but you should never edit it for this purpose, use `Make.user` instead. + +Julia's Makefiles define convenient automatic rules called `print-` for printing the value of variables, replacing `` with the name of the variable to print the value of. +For example +```console +$ make print-JULIA_PRECOMPILE +JULIA_PRECOMPILE=1 +``` +These rules are useful for debugging purposes. + Now you should be able to run Julia like this: julia @@ -239,10 +249,48 @@ For packaging Julia with LLVM, we recommend either: - bundling a Julia-only LLVM library inside the Julia package, or - adding the patches to the LLVM package of the distribution. * A complete list of patches is available in on [Github](https://github.com/JuliaLang/llvm-project) see the `julia-release/15.x` branch. - * The only Julia-specific patch is the lib renaming (`llvm-symver-jlprefix.patch`), which should _not_ be applied to a system LLVM. + * The only Julia-specific patch is the lib renaming (`llvm7-symver-jlprefix.patch`), which should _not_ be applied to a system LLVM. * The remaining patches are all upstream bug fixes, and have been contributed into upstream LLVM. -Using an unpatched or different version of LLVM will result in errors and/or poor performance. Though Julia can be built with newer LLVM versions, support for this should be regarded as experimental and not suitable for packaging. +Using an unpatched or different version of LLVM will result in errors and/or poor performance. +You can build a different version of LLVM from a remote Git repository with the following options in the `Make.user` file: + +```make +# Force source build of LLVM +USE_BINARYBUILDER_LLVM = 0 +# Use Git for fetching LLVM source code +# this is either `1` to get all of them +DEPS_GIT = 1 +# or a space-separated list of specific dependencies to download with git +DEPS_GIT = llvm + +# Other useful options: +#URL of the Git repository you want to obtain LLVM from: +# LLVM_GIT_URL = ... +#Name of the alternate branch to clone from git +# LLVM_BRANCH = julia-16.0.6-0 +#SHA hash of the alterate commit to check out automatically +# LLVM_SHA1 = $(LLVM_BRANCH) +#List of LLVM targets to build. It is strongly recommended to keep at least all the +#default targets listed in `deps/llvm.mk`, even if you don't necessarily need all of them. +# LLVM_TARGETS = ... +#Use ccache for faster recompilation in case you need to restart a build. +# USECCACHE = 1 +# CMAKE_GENERATOR=Ninja +# LLVM_ASSERTIONS=1 +# LLVM_DEBUG=Symbols +``` + +The various build phases are controlled by specific files: + * `deps/llvm.version` : touch or change to checkout a new version, `make get-llvm check-llvm` + * `deps/srccache/llvm/source-extracted` : result of `make extract-llvm` + * `deps/llvm/build_Release*/build-configured` : result of `make configure-llvm` + * `deps/llvm/build_Release*/build-configured` : result of `make compile-llvm` + * `usr-staging/llvm/build_Release*.tgz` : result of `make stage-llvm` (regenerate with `make reinstall-llvm`) + * `usr/manifest/llvm` : result of `make install-llvm` (regenerate with `make uninstall-llvm`) + * `make version-check-llvm` : runs every time to warn the user if there are local modifications + +Though Julia can be built with newer LLVM versions, support for this should be regarded as experimental and not suitable for packaging. ### libuv diff --git a/doc/src/devdocs/img/precompilation_hang.png b/doc/src/devdocs/img/precompilation_hang.png new file mode 100644 index 0000000000000..d076b7697f271 Binary files /dev/null and b/doc/src/devdocs/img/precompilation_hang.png differ diff --git a/doc/src/devdocs/precompile_hang.md b/doc/src/devdocs/precompile_hang.md new file mode 100644 index 0000000000000..0de9c99792b64 --- /dev/null +++ b/doc/src/devdocs/precompile_hang.md @@ -0,0 +1,98 @@ +# Fixing precompilation hangs due to open tasks or IO + +On Julia 1.10 or higher, you might see the following message: + +![Screenshot of precompilation hang](./img/precompilation_hang.png) + +This may repeat. If it continues to repeat with no hints that it will +resolve itself, you may have a "precompilation hang" that requires +fixing. Even if it's transient, you might prefer to resolve it so that +users will not be bothered by this warning. This page walks you +through how to analyze and fix such issues. + +If you follow the advice and hit `Ctrl-C`, you might see + +``` +^C Interrupted: Exiting precompilation... + + 1 dependency had warnings during precompilation: +┌ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982] +│ [pid 2745] waiting for IO to finish: +│ Handle type uv_handle_t->data +│ timer 0x55580decd1e0->0x7f94c3a4c340 +``` + +This message conveys two key pieces of information: + +- the hang is occurring during precompilation of `Test1`, a dependency of `Test2` (the package we were trying to load with `using Test2`) +- during precompilation of `Test1`, Julia created a `Timer` object (use `?Timer` if you're unfamiliar with Timers) which is still open; until that closes, the process is hung + +If this is enough of a hint for you to figure out how `timer = Timer(args...)` is being created, one good solution is to add `wait(timer)` if `timer` eventually finishes on its own, or `close(timer)` if you need to force-close it, before the final `end` of the module. + +However, there are cases that may not be that straightforward. Usually the best option is to start by determining whether the hang is due to code in Test1 or whether it is due to one of Test1's dependencies: + +- Option 1: `Pkg.add("Aqua")` and use [`Aqua.test_persistent_tasks`](https://juliatesting.github.io/Aqua.jl/dev/#Aqua.test_persistent_tasks-Tuple{Base.PkgId}). This should help you identify which package is causing the problem, after which the instructions [below](@ref pchang_fix) should be followed. If needed, you can create a `PkgId` as `Base.PkgId(UUID("..."), "Test1")`, where `...` comes from the `uuid` entry in `Test1/Project.toml`. +- Option 2: manually diagnose the source of the hang. + +To manually diagnose: + +1. `Pkg.develop("Test1")` +2. Comment out all the code `include`d or defined in `Test1`, *except* the `using/import` statements. +3. Try `using Test2` (or even `using Test1` assuming that hangs too) again + +Now we arrive at a fork in the road: either + +- the hang persists, indicating it is [due to one of your dependencies](@ref pchang_deps) +- the hang disappears, indicating that it is [due to something in your code](@ref pchang_fix). + +## [Diagnosing and fixing hangs due to a package dependency](@id pchang_deps) + +Use a binary search to identify the problematic dependency: start by commenting out half your dependencies, then when you isolate which half is responsible comment out half of that half, etc. (You don't have to remove them from the project, just comment out the `using`/`import` statements.) + +Once you've identified a suspect (here we'll call it `ThePackageYouThinkIsCausingTheProblem`), first try precompiling that package. If it also hangs during precompilation, continue chasing the problem backwards. + +However, most likely `ThePackageYouThinkIsCausingTheProblem` will precompile fine. This suggests it's in the function `ThePackageYouThinkIsCausingTheProblem.__init__`, which does not run during precompilation of `ThePackageYouThinkIsCausingTheProblem` but *does* in any package that loads `ThePackageYouThinkIsCausingTheProblem`. To test this theory, set up a minimal working example (MWE), something like + +```julia +(@v1.10) pkg> generate MWE + Generating project MWE: + MWE\Project.toml + MWE\src\MWE.jl +``` + +where the source code of `MWE.jl` is + +```julia +module MWE +using ThePackageYouThinkIsCausingTheProblem +end +``` + +and you've added `ThePackageYouThinkIsCausingTheProblem` to MWE's dependencies. + +If that MWE reproduces the hang, you've found your culprit: +`ThePackageYouThinkIsCausingTheProblem.__init__` must be creating the `Timer` object. If the timer object can be safely `close`d, that's a good option. Otherwise, the most common solution is to avoid creating the timer while *any* package is being precompiled: add + +```julia +ccall(:jl_generating_output, Cint, ()) == 1 && return nothing +``` + +as the first line of `ThePackageYouThinkIsCausingTheProblem.__init__`, and it will avoid doing any initialization in any Julia process whose purpose is to precompile packages. + +## [Fixing package code to avoid hangs](@id pchang_fix) + +Search your package for suggestive words (here like "Timer") and see if you can identify where the problem is being created. Note that a method *definition* like + +```julia +maketimer() = Timer(timer -> println("hi"), 0; interval=1) +``` + +is not problematic in and of itself: it can cause this problem only if `maketimer` gets called while the module is being defined. This might be happening from a top-level statement such as + +```julia +const GLOBAL_TIMER = maketimer() +``` + +or it might conceivably occur in a [precompile workload](https://github.com/JuliaLang/PrecompileTools.jl). + +If you struggle to identify the causative lines, then consider doing a binary search: comment out sections of your package (or `include` lines to omit entire files) until you've reduced the problem in scope. diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index a724f450dccfa..97f84f35f3eef 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -102,6 +102,9 @@ As a common convention in Julia (not a syntactic requirement), such a function w [typically be named `f!(x, y)`](@ref man-punctuation) rather than `f(x, y)`, as a visual reminder at the call site that at least one of the arguments (often the first one) is being mutated. +!!! warning "Shared memory between arguments" + The behavior of a mutating function can be unexpected when a mutated argument shares memory with another argument, a situation known as aliasing (e.g. when one is a view of the other). + Unless the function docstring explicitly indicates that aliasing produces the expected result, it is the responsibility of the caller to ensure proper behavior on such inputs. ## Argument-type declarations diff --git a/src/Makefile b/src/Makefile index 3ea28e7c40324..08fb816d302a0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -43,7 +43,7 @@ endif SRCS := \ jltypes gf typemap smallintset ast builtins module interpreter symbol \ dlload sys init task array staticdata toplevel jl_uv datatype \ - simplevector runtime_intrinsics precompile jloptions \ + simplevector runtime_intrinsics precompile jloptions mtarraylist \ threading partr stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler method \ jlapi signal-handling safepoint timing subtype rtutils gc-heap-snapshot \ crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage runtime_ccall diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 05088394500c1..5fc9a30fc35c3 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -835,8 +835,12 @@ static SmallVector partitionModule(Module &M, unsigned threads) { continue; if (!canPartition(G)) continue; - G.setLinkage(GlobalValue::ExternalLinkage); - G.setVisibility(GlobalValue::HiddenVisibility); + // Currently ccallable global aliases have extern linkage, we only want to make the + // internally linked functions/global variables extern+hidden + if (G.hasLocalLinkage()) { + G.setLinkage(GlobalValue::ExternalLinkage); + G.setVisibility(GlobalValue::HiddenVisibility); + } if (auto F = dyn_cast(&G)) { partitioner.make(&G, getFunctionWeight(*F).weight); } else { @@ -1569,6 +1573,16 @@ void jl_dump_native_impl(void *native_code, Type *T_psize = dataM.getDataLayout().getIntPtrType(Context)->getPointerTo(); + // This should really be in jl_create_native, but we haven't + // yet set the target triple binary format correctly at that + // point. This should be resolved when we start JITting for + // COFF when we switch over to JITLink. + for (auto &GA : dataM.aliases()) { + // Global aliases are only used for ccallable things, so we should + // mark them as dllexport + addComdat(&GA, TheTriple); + } + // add metadata information if (imaging_mode) { multiversioning_preannotate(dataM); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 7b15ef366777c..bb96805bbb72b 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -760,9 +760,6 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, lty = JuliaType::get_prjlvalue_ty(ctxt); isvector = false; } - else if (ty == (jl_value_t*)jl_bool_type) { - lty = getInt8Ty(ctxt); - } else if (jl_is_uniontype(ty)) { // pick an Integer type size such that alignment will generally be correct, // and always end with an Int8 (selector byte). diff --git a/src/codegen.cpp b/src/codegen.cpp index d28971b9a4caf..53a278d42d102 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1926,9 +1926,12 @@ static bool valid_as_globalinit(const Value *v) { return isa(v); } +static Value *zext_struct(jl_codectx_t &ctx, Value *V); + static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, Value *v, jl_value_t *typ, Value *tindex) { Value *loc; + v = zext_struct(ctx, v); if (valid_as_globalinit(v)) { // llvm can't handle all the things that could be inside a ConstantExpr assert(jl_is_concrete_type(typ)); // not legal to have an unboxed abstract type loc = get_pointer_to_constant(ctx.emission_context, cast(v), Align(julia_alignment(typ)), "_j_const", *jl_Module); @@ -2054,17 +2057,6 @@ static void alloc_def_flag(jl_codectx_t &ctx, jl_varinfo_t& vi) // --- utilities --- -static Constant *undef_value_for_type(Type *T) { - auto tracked = CountTrackedPointers(T); - Constant *undef; - if (tracked.count) - // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL - undef = Constant::getNullValue(T); - else - undef = UndefValue::get(T); - return undef; -} - static void CreateTrap(IRBuilder<> &irbuilder, bool create_new_block) { Function *f = irbuilder.GetInsertBlock()->getParent(); @@ -3346,7 +3338,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (f == jl_builtin_is && nargs == 2) { // emit comparison test Value *ans = emit_f_is(ctx, argv[1], argv[2]); - *ret = mark_julia_type(ctx, ctx.builder.CreateZExt(ans, getInt8Ty(ctx.builder.getContext())), false, jl_bool_type); + *ret = mark_julia_type(ctx, ans, false, jl_bool_type); return true; } @@ -3385,8 +3377,6 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (jl_is_type_type(ty.typ) && !jl_has_free_typevars(ty.typ)) { jl_value_t *tp0 = jl_tparam0(ty.typ); Value *isa_result = emit_isa(ctx, arg, tp0, NULL).first; - if (isa_result->getType() == getInt1Ty(ctx.builder.getContext())) - isa_result = ctx.builder.CreateZExt(isa_result, getInt8Ty(ctx.builder.getContext())); *ret = mark_julia_type(ctx, isa_result, false, jl_bool_type); return true; } @@ -3840,7 +3830,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else if (fld.typ == (jl_value_t*)jl_symbol_type) { // Known type but unknown symbol if (jl_is_datatype(utt) && (utt != jl_module_type) && jl_struct_try_layout(utt)) { - if ((jl_datatype_nfields(utt) == 1 && !jl_is_namedtuple_type(utt))) { + if ((jl_datatype_nfields(utt) == 1 && !jl_is_namedtuple_type(utt) && !jl_is_tuple_type(utt))) { jl_svec_t *fn = jl_field_names(utt); assert(jl_svec_len(fn) == 1); Value *typ_sym = literal_pointer_val(ctx, jl_svecref(fn, 0)); @@ -5271,16 +5261,15 @@ static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const s emit_typecheck(ctx, condV, (jl_value_t*)jl_bool_type, msg); } if (isbool) { - Value *cond = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); - assert(cond->getType() == getInt8Ty(ctx.builder.getContext())); - return ctx.builder.CreateXor(ctx.builder.CreateTrunc(cond, getInt1Ty(ctx.builder.getContext())), ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1)); + Value *cond = emit_unbox(ctx, getInt1Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); + return ctx.builder.CreateNot(cond); } if (condV.isboxed) { return ctx.builder.CreateICmpEQ(boxed(ctx, condV), track_pjlvalue(ctx, literal_pointer_val(ctx, jl_false))); } - // not a boolean - return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); // TODO: replace with Undef + // not a boolean (unreachable dead code) + return UndefValue::get(getInt1Ty(ctx.builder.getContext())); } static Value *emit_condition(jl_codectx_t &ctx, jl_value_t *cond, const std::string &msg) @@ -6797,6 +6786,10 @@ const char *jl_generate_ccallable(LLVMOrcThreadSafeModuleRef llvmmod, void *sysi int found = jl_dlsym(sysimg_handle, name, &addr, 0); if (found) add_named_global(name, addr); + else { + err = jl_get_exceptionf(jl_errorexception_type, "%s not found in sysimg", name); + jl_throw(err); + } } else { jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0); @@ -9294,6 +9287,8 @@ extern "C" JL_DLLEXPORT_CODEGEN void jl_teardown_codegen_impl() JL_NOTSAFEPOINT if (jl_ExecutionEngine) jl_ExecutionEngine->printTimers(); PrintStatistics(); + JL_LOCK(&jl_codegen_lock); // TODO: If this lock gets removed reconsider + // LLVM global state/destructors (maybe a rwlock) } // the rest of this file are convenience functions diff --git a/src/gc-stacks.c b/src/gc-stacks.c index b35c1722c82ff..b89ae8cb08e0d 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -112,7 +112,7 @@ static void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) if (bufsz <= pool_sizes[JL_N_STACK_POOLS - 1]) { unsigned pool_id = select_pool(bufsz); if (pool_sizes[pool_id] == bufsz) { - arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf); + small_arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf); return; } } @@ -141,7 +141,7 @@ void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task) #ifdef _COMPILER_ASAN_ENABLED_ __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); #endif - arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf); + small_arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf); } } } @@ -156,9 +156,9 @@ JL_DLLEXPORT void *jl_malloc_stack(size_t *bufsz, jl_task_t *owner) JL_NOTSAFEPO if (ssize <= pool_sizes[JL_N_STACK_POOLS - 1]) { unsigned pool_id = select_pool(ssize); ssize = pool_sizes[pool_id]; - arraylist_t *pool = &ptls->heap.free_stacks[pool_id]; + small_arraylist_t *pool = &ptls->heap.free_stacks[pool_id]; if (pool->len > 0) { - stk = arraylist_pop(pool); + stk = small_arraylist_pop(pool); } } else { @@ -177,8 +177,8 @@ JL_DLLEXPORT void *jl_malloc_stack(size_t *bufsz, jl_task_t *owner) JL_NOTSAFEPO } *bufsz = ssize; if (owner) { - arraylist_t *live_tasks = &ptls->heap.live_tasks; - arraylist_push(live_tasks, owner); + small_arraylist_t *live_tasks = &ptls->heap.live_tasks; + mtarraylist_push(live_tasks, owner); } return stk; } @@ -202,7 +202,7 @@ void sweep_stack_pools(void) // free half of stacks that remain unused since last sweep for (int p = 0; p < JL_N_STACK_POOLS; p++) { - arraylist_t *al = &ptls2->heap.free_stacks[p]; + small_arraylist_t *al = &ptls2->heap.free_stacks[p]; size_t n_to_free; if (al->len > MIN_STACK_MAPPINGS_PER_POOL) { n_to_free = al->len / 2; @@ -213,12 +213,12 @@ void sweep_stack_pools(void) n_to_free = 0; } for (int n = 0; n < n_to_free; n++) { - void *stk = arraylist_pop(al); + void *stk = small_arraylist_pop(al); free_stack(stk, pool_sizes[p]); } } - arraylist_t *live_tasks = &ptls2->heap.live_tasks; + small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; size_t n = 0; size_t ndel = 0; size_t l = live_tasks->len; @@ -261,24 +261,52 @@ void sweep_stack_pools(void) JL_DLLEXPORT jl_array_t *jl_live_tasks(void) { - jl_task_t *ct = jl_current_task; - jl_ptls_t ptls = ct->ptls; - arraylist_t *live_tasks = &ptls->heap.live_tasks; - size_t i, j, l; - jl_array_t *a; - do { - l = live_tasks->len; - a = jl_alloc_vec_any(l + 1); // may gc, changing the number of tasks - } while (l + 1 < live_tasks->len); - l = live_tasks->len; - void **lst = live_tasks->items; - j = 0; - ((void**)jl_array_data(a))[j++] = ptls->root_task; - for (i = 0; i < l; i++) { - if (((jl_task_t*)lst[i])->stkbuf != NULL) - ((void**)jl_array_data(a))[j++] = lst[i]; + size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); + jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); + size_t l = 0; // l is not reset on restart, so we keep getting more aggressive at making a big enough list everything it fails +restart: + for (size_t i = 0; i < nthreads; i++) { + // skip GC threads since they don't have tasks + if (gc_first_tid <= i && i < gc_first_tid + jl_n_gcthreads) { + continue; + } + jl_ptls_t ptls2 = allstates[i]; + if (ptls2 == NULL) + continue; + small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; + size_t n = mtarraylist_length(live_tasks); + l += n + (ptls2->root_task->stkbuf != NULL); + } + l += l / 20; // add 5% for margin of estimation error + jl_array_t *a = jl_alloc_vec_any(l); // may gc, changing the number of tasks and forcing us to reload everything + nthreads = jl_atomic_load_acquire(&jl_n_threads); + allstates = jl_atomic_load_relaxed(&jl_all_tls_states); + size_t j = 0; + for (size_t i = 0; i < nthreads; i++) { + // skip GC threads since they don't have tasks + if (gc_first_tid <= i && i < gc_first_tid + jl_n_gcthreads) { + continue; + } + jl_ptls_t ptls2 = allstates[i]; + if (ptls2 == NULL) + continue; + jl_task_t *t = ptls2->root_task; + if (t->stkbuf != NULL) { + if (j == l) + goto restart; + ((void**)jl_array_data(a))[j++] = t; + } + small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; + size_t n = mtarraylist_length(live_tasks); + for (size_t i = 0; i < n; i++) { + jl_task_t *t = (jl_task_t*)mtarraylist_get(live_tasks, i); + if (t->stkbuf != NULL) { + if (j == l) + goto restart; + ((void**)jl_array_data(a))[j++] = t; + } + } } - l = jl_array_len(a); if (j < l) { JL_GC_PUSH1(&a); jl_array_del_end(a, l - j); diff --git a/src/gc.c b/src/gc.c index a112d73de625a..197d1c6ad75c2 100644 --- a/src/gc.c +++ b/src/gc.c @@ -420,7 +420,7 @@ JL_DLLEXPORT void jl_gc_init_finalizer_rng_state(void) jl_rng_split(finalizer_rngState, jl_current_task->rngState); } -static void run_finalizers(jl_task_t *ct) +static void run_finalizers(jl_task_t *ct, int finalizers_thread) { // Racy fast path: // The race here should be OK since the race can only happen if @@ -448,7 +448,7 @@ static void run_finalizers(jl_task_t *ct) // This releases the finalizers lock. int8_t was_in_finalizer = ct->ptls->in_finalizer; - ct->ptls->in_finalizer = 1; + ct->ptls->in_finalizer = !finalizers_thread; jl_gc_run_finalizers_in_list(ct, &copied_list); ct->ptls->in_finalizer = was_in_finalizer; arraylist_free(&copied_list); @@ -462,7 +462,7 @@ JL_DLLEXPORT void jl_gc_run_pending_finalizers(jl_task_t *ct) ct = jl_current_task; jl_ptls_t ptls = ct->ptls; if (!ptls->in_finalizer && ptls->locks.len == 0 && ptls->finalizers_inhibited == 0) { - run_finalizers(ct); + run_finalizers(ct, 0); } } @@ -555,7 +555,7 @@ void jl_gc_run_all_finalizers(jl_task_t *ct) JL_UNLOCK_NOGC(&finalizers_lock); gc_n_threads = 0; gc_all_tls_states = NULL; - run_finalizers(ct); + run_finalizers(ct, 1); } void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT @@ -947,7 +947,7 @@ JL_DLLEXPORT jl_weakref_t *jl_gc_new_weakref_th(jl_ptls_t ptls, jl_weakref_t *wr = (jl_weakref_t*)jl_gc_alloc(ptls, sizeof(void*), jl_weakref_type); wr->value = value; // NOTE: wb not needed here - arraylist_push(&ptls->heap.weak_refs, wr); + small_arraylist_push(&ptls->heap.weak_refs, wr); return wr; } @@ -2278,19 +2278,10 @@ STATIC_INLINE void gc_mark_excstack(jl_ptls_t ptls, jl_excstack_t *excstack, siz } // Mark module binding -STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent, jl_binding_t **mb_begin, - jl_binding_t **mb_end, uintptr_t nptr, +STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent, uintptr_t nptr, uint8_t bits) JL_NOTSAFEPOINT { jl_gc_markqueue_t *mq = &ptls->mark_queue; - for (; mb_begin < mb_end; mb_begin++) { - jl_binding_t *b = *mb_begin; - if (b == (jl_binding_t *)jl_nothing) - continue; - verify_parent1("module", mb_parent, mb_begin, "binding_buff"); - gc_assert_parent_validity((jl_value_t *)mb_parent, (jl_value_t *)b); - gc_try_claim_and_push(mq, b, &nptr); - } jl_value_t *bindings = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindings); gc_assert_parent_validity((jl_value_t *)mb_parent, bindings); gc_try_claim_and_push(mq, bindings, &nptr); @@ -2422,13 +2413,8 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ else if (foreign_alloc) objprofile_count(jl_module_type, bits == GC_OLD_MARKED, sizeof(jl_module_t)); jl_module_t *mb_parent = (jl_module_t *)new_obj; - jl_svec_t *bindings = jl_atomic_load_relaxed(&mb_parent->bindings); - jl_binding_t **table = (jl_binding_t**)jl_svec_data(bindings); - size_t bsize = jl_svec_len(bindings); - uintptr_t nptr = ((bsize + mb_parent->usings.len + 1) << 2) | (bits & GC_OLD); - jl_binding_t **mb_begin = table + 1; - jl_binding_t **mb_end = table + bsize; - gc_mark_module_binding(ptls, mb_parent, mb_begin, mb_end, nptr, bits); + uintptr_t nptr = ((mb_parent->usings.len + 1) << 2) | (bits & GC_OLD); + gc_mark_module_binding(ptls, mb_parent, nptr, bits); } else if (vtag == jl_task_tag << 4) { if (update_meta) @@ -3466,7 +3452,7 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) // or wait for finalizers on other threads without dead lock). if (!ptls->finalizers_inhibited && ptls->locks.len == 0) { JL_TIMING(GC, GC_Finalizers); - run_finalizers(ct); + run_finalizers(ct, 0); } JL_PROBE_GC_FINALIZER(); @@ -3510,8 +3496,10 @@ void jl_init_thread_heap(jl_ptls_t ptls) p[i].freelist = NULL; p[i].newpages = NULL; } - arraylist_new(&heap->weak_refs, 0); - arraylist_new(&heap->live_tasks, 0); + small_arraylist_new(&heap->weak_refs, 0); + small_arraylist_new(&heap->live_tasks, 0); + for (int i = 0; i < JL_N_STACK_POOLS; i++) + small_arraylist_new(&heap->free_stacks[i], 0); heap->mallocarrays = NULL; heap->mafreelist = NULL; heap->big_objects = NULL; @@ -3866,8 +3854,7 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o errno = last_errno; jl_may_leak(base); assert(align > 0); - unsigned diff = (offset - (uintptr_t)base) % align; - return (void*)((char*)base + diff); + return (void*)(LLT_ALIGN((uintptr_t)base + offset, (uintptr_t)align) - offset); } STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT diff --git a/src/gf.c b/src/gf.c index 9ee77a8426432..1b6251078fa1f 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1568,8 +1568,10 @@ static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue jl_printf(s, ".\n"); jl_uv_flush(s); } - if (jl_generating_output()) - jl_error("Method overwriting is not permitted during Module precompile."); + if (jl_generating_output()) { + jl_printf(JL_STDERR, "ERROR: Method overwriting is not permitted during Module precompilation. Use `__precompile__(false)` to opt-out of precompilation.\n"); + jl_throw(jl_precompilable_error); + } } static void update_max_args(jl_methtable_t *mt, jl_value_t *type) diff --git a/src/init.c b/src/init.c index 4a152ed04b13d..c49ec2ac80be2 100644 --- a/src/init.c +++ b/src/init.c @@ -574,6 +574,14 @@ static char *abspath(const char *in, int nprefix) } } #else + // GetFullPathName intentionally errors if given an empty string so manually insert `.` to invoke cwd + char *in2 = (char*)malloc_s(JL_PATH_MAX); + if (strlen(in) - nprefix == 0) { + memcpy(in2, in, nprefix); + in2[nprefix] = '.'; + in2[nprefix+1] = '\0'; + in = in2; + } DWORD n = GetFullPathName(in + nprefix, 0, NULL, NULL); if (n <= 0) { jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); @@ -584,6 +592,7 @@ static char *abspath(const char *in, int nprefix) jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); } memcpy(out, in, nprefix); + free(in2); #endif return out; } @@ -678,7 +687,7 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) if (jl_options.output_code_coverage) jl_options.output_code_coverage = absformat(jl_options.output_code_coverage); if (jl_options.tracked_path) - jl_options.tracked_path = absformat(jl_options.tracked_path); + jl_options.tracked_path = abspath(jl_options.tracked_path, 0); const char **cmdp = jl_options.cmds; if (cmdp) { diff --git a/src/interpreter.c b/src/interpreter.c index 2ad56e76b2549..d84a1381fccad 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -65,7 +65,8 @@ extern void JL_GC_ENABLEFRAME(interpreter_state*) JL_NOTSAFEPOINT; // we define this separately so that we can populate the frame before we add it to the backtrace // it's recommended to mark the containing function with NOINLINE, though not essential #define JL_GC_ENABLEFRAME(frame) \ - ((void**)&frame[1])[0] = __builtin_frame_address(0); + jl_signal_fence(); \ + ((void**)&frame[1])[0] = __builtin_frame_address(0); #endif diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 810982370de19..9c163458f22d2 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -174,12 +174,7 @@ static Type *INTT(Type *t, const DataLayout &DL) static Value *uint_cnvt(jl_codectx_t &ctx, Type *to, Value *x) { - Type *t = x->getType(); - if (t == to) - return x; - if (to->getPrimitiveSizeInBits() < x->getType()->getPrimitiveSizeInBits()) - return ctx.builder.CreateTrunc(x, to); - return ctx.builder.CreateZExt(x, to); + return ctx.builder.CreateZExtOrTrunc(x, to); } static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_datatype_t *bt) @@ -318,25 +313,90 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, jl_value_t *e) return julia_const_to_llvm(ctx, e, (jl_datatype_t*)bt); } +static Constant *undef_value_for_type(Type *T) { + auto tracked = CountTrackedPointers(T); + Constant *undef; + if (tracked.count) + // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL + undef = Constant::getNullValue(T); + else + undef = UndefValue::get(T); + return undef; +} + +// rebuild a struct type with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Type *zext_struct_type(Type *T) +{ + if (auto *AT = dyn_cast(T)) { + return ArrayType::get(AT->getElementType(), AT->getNumElements()); + } + else if (auto *ST = dyn_cast(T)) { + SmallVector Elements(ST->element_begin(), ST->element_end()); + for (size_t i = 0; i < Elements.size(); i++) { + Elements[i] = zext_struct_type(Elements[i]); + } + return StructType::get(ST->getContext(), Elements, ST->isPacked()); + } + else if (auto *VT = dyn_cast(T)) { + return VectorType::get(zext_struct_type(VT->getElementType()), VT); + } + else if (auto *IT = dyn_cast(T)) { + unsigned BitWidth = IT->getBitWidth(); + if (alignTo(BitWidth, 8) != BitWidth) + return IntegerType::get(IT->getContext(), alignTo(BitWidth, 8)); + } + return T; +} + +// rebuild a struct with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Value *zext_struct_helper(jl_codectx_t &ctx, Value *V, Type *T2) +{ + Type *T = V->getType(); + if (T == T2) + return V; + if (auto *AT = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(AT); + for (size_t i = 0; i < AT->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), AT->getElementType()); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (auto *ST = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(ST); + for (size_t i = 0; i < ST->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), ST->getElementType(i)); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (T2->isIntegerTy() || T2->isVectorTy()) { + return ctx.builder.CreateZExt(V, T2); + } + return V; +} + +static Value *zext_struct(jl_codectx_t &ctx, Value *V) +{ + return zext_struct_helper(ctx, V, zext_struct_type(V->getType())); +} + static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) { + if (unboxed->getType() == to) + return unboxed; + if (CastInst::castIsValid(Instruction::Trunc, unboxed, to)) + return ctx.builder.CreateTrunc(unboxed, to); + unboxed = zext_struct(ctx, unboxed); Type *ty = unboxed->getType(); if (ty == to) return unboxed; bool frompointer = ty->isPointerTy(); bool topointer = to->isPointerTy(); const DataLayout &DL = jl_Module->getDataLayout(); - if (ty->isIntegerTy(1) && to->isIntegerTy(8)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateZExt(unboxed, to); - } - else if (ty->isIntegerTy(8) && to->isIntegerTy(1)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateTrunc(unboxed, to); - } - else if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { + if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { // this can happen in dead code - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); } if (frompointer && topointer) { @@ -381,7 +441,7 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va if (type_is_ghost(to)) { return NULL; } - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); // type mismatch error } @@ -447,17 +507,9 @@ static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest return; } - Value *unboxed = nullptr; - if (!x.ispointer()) { // already unboxed, but sometimes need conversion - unboxed = x.V; - assert(unboxed); - } - - // bools stored as int8, but can be narrowed to int1 often - if (x.typ == (jl_value_t*)jl_bool_type) - unboxed = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), x, (jl_value_t*)jl_bool_type); - - if (unboxed) { + if (!x.ispointer()) { // already unboxed, but sometimes need conversion (e.g. f32 -> i32) + assert(x.V); + Value *unboxed = zext_struct(ctx, x.V); Type *dest_ty = unboxed->getType()->getPointerTo(); if (dest->getType() != dest_ty) dest = emit_bitcast(ctx, dest, dest_ty); @@ -1455,12 +1507,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg Intrinsic::smul_with_overflow : Intrinsic::umul_with_overflow))))); FunctionCallee intr = Intrinsic::getDeclaration(jl_Module, intr_id, makeArrayRef(t)); - Value *res = ctx.builder.CreateCall(intr, {x, y}); - Value *val = ctx.builder.CreateExtractValue(res, ArrayRef(0)); - setName(ctx.emission_context, val, "checked"); - Value *obit = ctx.builder.CreateExtractValue(res, ArrayRef(1)); - setName(ctx.emission_context, obit, "overflow"); - Value *obyte = ctx.builder.CreateZExt(obit, getInt8Ty(ctx.builder.getContext())); + Value *tupval = ctx.builder.CreateCall(intr, {x, y}); jl_value_t *params[2]; params[0] = xtyp; @@ -1468,10 +1515,6 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); *newtyp = tuptyp; - Value *tupval; - tupval = UndefValue::get(julia_type_to_llvm(ctx, (jl_value_t*)tuptyp)); - tupval = ctx.builder.CreateInsertValue(tupval, val, ArrayRef(0)); - tupval = ctx.builder.CreateInsertValue(tupval, obyte, ArrayRef(1)); return tupval; } diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 1a385403e9672..099d74ccb37c1 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -1301,6 +1301,69 @@ namespace { JuliaOJIT::ResourcePool> TMs; }; + + struct JITPointersT { + + JITPointersT(SharedBytesT &SharedBytes, std::mutex &Lock) JL_NOTSAFEPOINT + : SharedBytes(SharedBytes), Lock(Lock) {} + + void operator()(Module &M) JL_NOTSAFEPOINT { + std::lock_guard locked(Lock); + for (auto &GV : make_early_inc_range(M.globals())) { + if (auto *Shared = getSharedBytes(GV)) { + ++InternedGlobals; + GV.replaceAllUsesWith(Shared); + GV.eraseFromParent(); + } + } + + // Windows needs some inline asm to help + // build unwind tables + jl_decorate_module(M); + } + + private: + // optimize memory by turning long strings into memoized copies, instead of + // making a copy per object file of output. + // we memoize them using a StringSet with a custom-alignment allocator + // to ensure they are properly aligned + Constant *getSharedBytes(GlobalVariable &GV) JL_NOTSAFEPOINT { + // We could probably technically get away with + // interning even external linkage globals, + // as long as they have global unnamedaddr, + // but currently we shouldn't be emitting those + // except in imaging mode, and we don't want to + // do this optimization there. + if (GV.hasExternalLinkage() || !GV.hasGlobalUnnamedAddr()) { + return nullptr; + } + if (!GV.hasInitializer()) { + return nullptr; + } + if (!GV.isConstant()) { + return nullptr; + } + auto CDS = dyn_cast(GV.getInitializer()); + if (!CDS) { + return nullptr; + } + StringRef Data = CDS->getRawDataValues(); + if (Data.size() < 16) { + // Cutoff, since we don't want to intern small strings + return nullptr; + } + Align Required = GV.getAlign().valueOrOne(); + Align Preferred = MaxAlignedAlloc::alignment(Data.size()); + if (Required > Preferred) + return nullptr; + StringRef Interned = SharedBytes.insert(Data).first->getKey(); + assert(llvm::isAddrAligned(Preferred, Interned.data())); + return literal_static_pointer_val(Interned.data(), GV.getType()); + } + + SharedBytesT &SharedBytes; + std::mutex &Lock; + }; } llvm::DataLayout jl_create_datalayout(TargetMachine &TM) { @@ -1493,8 +1556,7 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) ++ModulesAdded; orc::SymbolLookupSet NewExports; TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { - jl_decorate_module(M); - shareStrings(M); + JITPointersT(SharedBytes, RLST_mutex)(M); for (auto &F : M.global_values()) { if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { auto Name = ES.intern(getMangledName(F.getName())); @@ -1820,32 +1882,6 @@ void jl_merge_module(orc::ThreadSafeModule &destTSM, orc::ThreadSafeModule srcTS }); } -// optimize memory by turning long strings into memoized copies, instead of -// making a copy per object file of output. -void JuliaOJIT::shareStrings(Module &M) -{ - ++InternedGlobals; - std::vector erase; - for (auto &GV : M.globals()) { - if (!GV.hasInitializer() || !GV.isConstant()) - continue; - ConstantDataSequential *CDS = dyn_cast(GV.getInitializer()); - if (CDS == nullptr) - continue; - StringRef data = CDS->getRawDataValues(); - if (data.size() > 16) { // only for long strings: keep short ones as values - Type *T_size = Type::getIntNTy(GV.getContext(), sizeof(void*) * 8); - Constant *v = ConstantExpr::getIntToPtr( - ConstantInt::get(T_size, (uintptr_t)(*ES.intern(data)).data()), - GV.getType()); - GV.replaceAllUsesWith(v); - erase.push_back(&GV); - } - } - for (auto GV : erase) - GV->eraseFromParent(); -} - //TargetMachine pass-through methods std::unique_ptr JuliaOJIT::cloneTargetMachine() const diff --git a/src/jitlayers.h b/src/jitlayers.h index 380694af60742..f57861fa56794 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -1,6 +1,8 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include +#include +#include #include #include @@ -298,6 +300,46 @@ static const inline char *name_from_method_instance(jl_method_instance_t *li) JL return jl_is_method(li->def.method) ? jl_symbol_name(li->def.method->name) : "top-level scope"; } +template +class MaxAlignedAllocImpl + : public AllocatorBase> { + +public: + MaxAlignedAllocImpl() JL_NOTSAFEPOINT = default; + + static Align alignment(size_t Size) JL_NOTSAFEPOINT { + // Define the maximum alignment we expect to require, from offset bytes off + // the returned pointer, this is >= alignof(std::max_align_t), which is too + // small often to actually use. + const size_t MaxAlignment = JL_CACHE_BYTE_ALIGNMENT; + if (Size <= offset) + return Align(1); + return Align(std::min((size_t)llvm::PowerOf2Ceil(Size - offset), MaxAlignment)); + } + + LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, Align Alignment) { + Align MaxAlign = alignment(Size); + assert(Alignment < MaxAlign); (void)Alignment; + return jl_gc_perm_alloc(Size, 0, MaxAlign.value(), offset); + } + + inline LLVM_ATTRIBUTE_RETURNS_NONNULL + void * Allocate(size_t Size, size_t Alignment) { + return Allocate(Size, Align(Alignment)); + } + + // Pull in base class overloads. + using AllocatorBase::Allocate; + + void Deallocate(const void *Ptr, size_t Size, size_t /*Alignment*/) { abort(); } + + // Pull in base class overloads. + using AllocatorBase::Deallocate; + +private: +}; +using MaxAlignedAlloc = MaxAlignedAllocImpl<>; + typedef JITSymbol JL_JITSymbol; // The type that is similar to SymbolInfo on LLVM 4.0 is actually // `JITEvaluatedSymbol`. However, we only use this type when a JITSymbol @@ -306,6 +348,7 @@ typedef JITSymbol JL_SymbolInfo; using CompilerResultT = Expected>; using OptimizerResultT = Expected; +using SharedBytesT = StringSet::MapEntryTy)>>; class JuliaOJIT { public: @@ -538,7 +581,6 @@ class JuliaOJIT { private: std::string getMangledName(StringRef Name) JL_NOTSAFEPOINT; std::string getMangledName(const GlobalValue *GV) JL_NOTSAFEPOINT; - void shareStrings(Module &M) JL_NOTSAFEPOINT; const std::unique_ptr TM; const DataLayout DL; @@ -551,6 +593,7 @@ class JuliaOJIT { std::mutex RLST_mutex{}; int RLST_inc = 0; DenseMap ReverseLocalSymbolTable; + SharedBytesT SharedBytes; //Compilation streams jl_locked_stream dump_emitted_mi_name_stream; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 092a48be81930..b9d4651ef24c0 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -126,6 +126,7 @@ XX(jl_voidpointer_type) \ XX(jl_void_type) \ XX(jl_weakref_type) \ + XX(jl_precompilable_error) \ // Data symbols that are defined inside the public libjulia #define JL_EXPORTED_DATA_SYMBOLS(XX) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 745905b113622..ee255a0e1a876 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -214,6 +214,7 @@ XX(jl_get_binding_wr) \ XX(jl_get_cpu_name) \ XX(jl_get_cpu_features) \ + XX(jl_cpu_has_fma) \ XX(jl_get_current_task) \ XX(jl_get_default_sysimg_path) \ XX(jl_get_excstack) \ diff --git a/src/jl_uv.c b/src/jl_uv.c index 281dd798dbb36..62dc3a628d085 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -51,9 +51,9 @@ static void walk_print_cb(uv_handle_t *h, void *arg) npad += strlen(type); pad += npad < strlen(pad) ? npad : strlen(pad); if (fd == -1) - jl_safe_printf(" %s %s@%p->%p\n", type, pad, (void*)h, (void*)h->data); + jl_safe_printf(" %s %s%p->%p\n", type, pad, (void*)h, (void*)h->data); else - jl_safe_printf(" %s[%zd] %s@%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data); + jl_safe_printf(" %s[%zd] %s%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data); } static void wait_empty_func(uv_timer_t *t) @@ -63,9 +63,12 @@ static void wait_empty_func(uv_timer_t *t) if (!uv_loop_alive(t->loop)) return; jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n" - " TYPE[FD/PID] @UV_HANDLE_T->DATA\n", + " Handle type uv_handle_t->data\n", (size_t)uv_os_getpid()); uv_walk(jl_io_loop, walk_print_cb, NULL); + if (jl_generating_output() && jl_options.incremental) { + jl_safe_printf("This means that a package has started a background task or event source that has not finished running. For precompilation to complete successfully, the event source needs to be closed explicitly. See the developer documentation on fixing precompilation hangs for more help.\n"); + } jl_gc_collect(JL_GC_FULL); } diff --git a/src/jltypes.c b/src/jltypes.c index a39bc935fb181..d61a06b06f159 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3410,6 +3410,7 @@ void post_boot_hooks(void) jl_methoderror_type = (jl_datatype_t*)core("MethodError"); jl_loaderror_type = (jl_datatype_t*)core("LoadError"); jl_initerror_type = (jl_datatype_t*)core("InitError"); + jl_precompilable_error = jl_new_struct_uninit((jl_datatype_t*)core("PrecompilableError")); jl_pair_type = core("Pair"); jl_kwcall_func = core("kwcall"); jl_kwcall_mt = ((jl_datatype_t*)jl_typeof(jl_kwcall_func))->name->mt; diff --git a/src/julia.h b/src/julia.h index f4229eae909ac..1f85f06dd35dc 100644 --- a/src/julia.h +++ b/src/julia.h @@ -829,6 +829,7 @@ extern JL_DLLIMPORT jl_value_t *jl_readonlymemory_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_diverror_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_undefref_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_interrupt_exception JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_precompilable_error JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_boundserror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_an_empty_vec_any JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_an_empty_string JL_GLOBALLY_ROOTED; @@ -1049,6 +1050,11 @@ JL_DLLEXPORT void *jl_gc_managed_realloc(void *d, size_t sz, size_t oldsz, int isaligned, jl_value_t *owner); JL_DLLEXPORT void jl_gc_safepoint(void); +void *mtarraylist_get(small_arraylist_t *_a, size_t idx) JL_NOTSAFEPOINT; +size_t mtarraylist_length(small_arraylist_t *_a) JL_NOTSAFEPOINT; +void mtarraylist_add(small_arraylist_t *_a, void *elt, size_t idx) JL_NOTSAFEPOINT; +void mtarraylist_push(small_arraylist_t *_a, void *elt) JL_NOTSAFEPOINT; + // object accessors ----------------------------------------------------------- #define jl_svec_len(t) (((jl_svec_t*)(t))->length) diff --git a/src/julia_internal.h b/src/julia_internal.h index 3e4c01365a2fc..4a845350424d4 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -203,6 +203,8 @@ JL_DLLEXPORT void jl_lock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; JL_DLLEXPORT void jl_unlock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; JL_DLLEXPORT void jl_lock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; JL_DLLEXPORT void jl_unlock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; +int jl_lock_stackwalk(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +void jl_unlock_stackwalk(int lockret) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; // number of cycles since power-on static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT @@ -349,7 +351,7 @@ JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize) JL_NOTSAFEPOINT; extern uv_mutex_t gc_perm_lock; void *jl_gc_perm_alloc_nolock(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; -void *jl_gc_perm_alloc(size_t sz, int zero, +JL_DLLEXPORT void *jl_gc_perm_alloc(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; void gc_sweep_sysimg(void); @@ -1162,6 +1164,9 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_data) JL_NOTSAFEPOINT; #ifdef _OS_WINDOWS_ JL_DLLEXPORT void jl_refresh_dbg_module_list(void); #endif +int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) JL_NOTSAFEPOINT; +void jl_thread_resume(int tid) JL_NOTSAFEPOINT; + // *to is NULL or malloc'd pointer, from is allowed to be NULL STATIC_INLINE char *jl_copy_str(char **to, const char *from) JL_NOTSAFEPOINT { diff --git a/src/julia_threads.h b/src/julia_threads.h index f4c235243e684..8acbf9b53d90c 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -109,7 +109,7 @@ typedef struct { // handle to reference an OS thread #ifdef _OS_WINDOWS_ -typedef DWORD jl_thread_t; +typedef HANDLE jl_thread_t; #else typedef pthread_t jl_thread_t; #endif @@ -140,10 +140,10 @@ typedef struct { typedef struct { // variable for tracking weak references - arraylist_t weak_refs; + small_arraylist_t weak_refs; // live tasks started on this thread // that are holding onto a stack from the pool - arraylist_t live_tasks; + small_arraylist_t live_tasks; // variables for tracking malloc'd arrays struct _mallocarray_t *mallocarrays; @@ -170,7 +170,7 @@ typedef struct { jl_gc_pool_t norm_pools[JL_GC_N_POOLS]; #define JL_N_STACK_POOLS 16 - arraylist_t free_stacks[JL_N_STACK_POOLS]; + small_arraylist_t free_stacks[JL_N_STACK_POOLS]; } jl_thread_heap_t; typedef struct { diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index b87a5a6799b0b..751cae6419dda 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -643,8 +643,6 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) } insertLifetime(ptr, ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), sz), orig_inst); Instruction *new_inst = cast(prolog_builder.CreateBitCast(ptr, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); - if (orig_inst->getModule()->getDataLayout().getAllocaAddrSpace() != 0) - new_inst = cast(prolog_builder.CreateAddrSpaceCast(new_inst, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), orig_inst->getType()->getPointerAddressSpace()))); new_inst->takeName(orig_inst); auto simple_replace = [&] (Instruction *orig_i, Instruction *new_i) { @@ -692,7 +690,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) else if (auto call = dyn_cast(user)) { auto callee = call->getCalledOperand(); if (pass.pointer_from_objref_func == callee) { - call->replaceAllUsesWith(new_i); + call->replaceAllUsesWith(prolog_builder.CreateAddrSpaceCast(new_i, call->getCalledFunction()->getReturnType())); call->eraseFromParent(); return; } diff --git a/src/llvm-cpufeatures.cpp b/src/llvm-cpufeatures.cpp index 77f1baf6237c4..3d3fb6b0583da 100644 --- a/src/llvm-cpufeatures.cpp +++ b/src/llvm-cpufeatures.cpp @@ -60,7 +60,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS StringRef FS = FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); - SmallVector Features; + SmallVector Features; FS.split(Features, ','); for (StringRef Feature : Features) if (TT.isARM()) { @@ -68,7 +68,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS return typ == "f32" || typ == "f64"; else if (Feature == "+vfp4sp") return typ == "f32"; - } else { + } else if (TT.isX86()) { if (Feature == "+fma" || Feature == "+fma4") return typ == "f32" || typ == "f64"; } diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index e4ebbe9d3838a..accdef0aaaa83 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -531,7 +531,7 @@ void CloneCtx::clone_decls() new_F->setVisibility(F->getVisibility()); new_F->setDSOLocal(true); auto base_func = F; - if (specs[i].flags & JL_TARGET_CLONE_ALL) + if (!(specs[i].flags & JL_TARGET_CLONE_ALL)) base_func = static_cast(linearized[specs[i].base])->base_func(F); (*linearized[i]->vmap)[base_func] = new_F; } @@ -586,7 +586,7 @@ void CloneCtx::clone_bodies() } for (auto &target : groups[i].clones) { prepare_vmap(*target.vmap); - auto target_F = cast_or_null(map_get(*target.vmap, F)); + auto target_F = cast_or_null(map_get(*target.vmap, group_F)); if (target_F) { if (!F->isDeclaration()) { clone_function(group_F, target_F, *target.vmap); diff --git a/src/macroexpand.scm b/src/macroexpand.scm index e0e809eee08f1..6e390a6c24cf2 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -340,8 +340,22 @@ (define (reescape ux x) (if (and (pair? x) (eq? (car x) 'escape)) - (reescape '(escape ,ux) (cadr x))) - ux) + (reescape `(escape ,ux) (cadr x)) + ux)) + +;; type has special behavior: identifiers inside are +;; field names, not expressions. +(define (resolve-struct-field-expansion x env m parent-scope inarg) + (let ((ux (unescape x))) + (cond + ((atom? ux) ux) + ((and (pair? ux) (eq? (car ux) '|::|)) + `(|::| ,(unescape (cadr ux)) + ,(resolve-expansion-vars- (reescape (caddr ux) x) env m parent-scope inarg))) + ((and (pair? ux) (memq (car ux) '(const atomic))) + `(,(car ux) ,(resolve-struct-field-expansion (reescape (cadr ux) x) env m parent-scope inarg))) + (else + (resolve-expansion-vars-with-new-env x env m parent-scope inarg))))) (define (resolve-expansion-vars- e env m parent-scope inarg) (cond ((or (eq? e 'begin) (eq? e 'end) (eq? e 'ccall) (eq? e 'cglobal) (underscore-symbol? e)) @@ -377,16 +391,8 @@ ((symbolicgoto) e) ((struct) `(struct ,(cadr e) ,(resolve-expansion-vars- (caddr e) env m parent-scope inarg) - ;; type has special behavior: identifiers inside are - ;; field names, not expressions. ,(map (lambda (x) - (let ((ux (unescape x))) - (cond ((atom? ux) ux) - ((and (pair? ux) (eq? (car ux) '|::|)) - `(|::| ,(unescape (cadr ux)) - ,(resolve-expansion-vars- (reescape (caddr ux) x) env m parent-scope inarg))) - (else - (resolve-expansion-vars-with-new-env x env m parent-scope inarg))))) + (resolve-struct-field-expansion x env m parent-scope inarg)) (cadddr e)))) ((parameters) diff --git a/src/mtarraylist.c b/src/mtarraylist.c new file mode 100644 index 0000000000000..8bad44797dab4 --- /dev/null +++ b/src/mtarraylist.c @@ -0,0 +1,81 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "julia.h" +#include "julia_internal.h" +#include "julia_assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// this file provides some alternate API functions for small_arraylist (push and add) +// which can be safely observed from other threads concurrently +// there is only permitted to be a single writer thread (or a mutex) +// but there can be any number of observers + +typedef struct { + _Atomic(uint32_t) len; + uint32_t max; + _Atomic(_Atomic(void*)*) items; + _Atomic(void*) _space[SMALL_AL_N_INLINE]; +} small_mtarraylist_t; + +// change capacity to at least newlen +static void mtarraylist_resizeto(small_mtarraylist_t *a, size_t len, size_t newlen) JL_NOTSAFEPOINT +{ + size_t max = a->max; + if (newlen > max) { + size_t nm = max * 2; + if (nm == 0) + nm = 1; + while (newlen > nm) + nm *= 2; + void *olditems = (void*)jl_atomic_load_relaxed(&a->items); + void *p = calloc_s(nm * sizeof(void*)); + memcpy(p, olditems, len * sizeof(void*)); + jl_atomic_store_release(&a->items, (_Atomic(void*)*)p); + a->max = nm; + if (olditems != (void*)&a->_space[0]) { + jl_task_t *ct = jl_current_task; + jl_gc_add_quiescent(ct->ptls, (void**)olditems, free); + } + } +} + +// single-threaded +void mtarraylist_push(small_arraylist_t *_a, void *elt) +{ + small_mtarraylist_t *a = (small_mtarraylist_t*)_a; + size_t len = jl_atomic_load_relaxed(&a->len); + mtarraylist_resizeto(a, len, len + 1); + jl_atomic_store_release(&jl_atomic_load_relaxed(&a->items)[len], elt); + jl_atomic_store_release(&a->len, len + 1); +} + +// single-threaded +void mtarraylist_add(small_arraylist_t *_a, void *elt, size_t idx) +{ + small_mtarraylist_t *a = (small_mtarraylist_t*)_a; + size_t len = jl_atomic_load_relaxed(&a->len); + mtarraylist_resizeto(a, len, idx + 1); + jl_atomic_store_release(&jl_atomic_load_relaxed(&a->items)[idx], elt); + if (jl_atomic_load_relaxed(&a->len) < idx + 1) + jl_atomic_store_release(&a->len, idx + 1); +} + +// concurrent-safe +size_t mtarraylist_length(small_arraylist_t *_a) +{ + small_mtarraylist_t *a = (small_mtarraylist_t*)_a; + return jl_atomic_load_relaxed(&a->len); +} + +// concurrent-safe +void *mtarraylist_get(small_arraylist_t *_a, size_t idx) +{ + small_mtarraylist_t *a = (small_mtarraylist_t*)_a; + size_t len = jl_atomic_load_acquire(&a->len); + if (idx >= len) + return NULL; + return jl_atomic_load_relaxed(&jl_atomic_load_relaxed(&a->items)[idx]); +} diff --git a/src/processor.cpp b/src/processor.cpp index d2d91d3cd9966..c5b35f57799e8 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -634,7 +634,7 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) JL_GC_PUSH1(&rejection_reason); uint32_t target_idx = callback(ids, &rejection_reason); if (target_idx == (uint32_t)-1) { - jl_throw(jl_new_struct(jl_errorexception_type, rejection_reason)); + jl_error(jl_string_ptr(rejection_reason)); } JL_GC_POP(); @@ -779,7 +779,10 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) if (!clones.empty()) { assert(!fvars.empty()); - std::sort(clones.begin(), clones.end()); + std::sort(clones.begin(), clones.end(), + [](const std::pair &a, const std::pair &b) { + return (a.first & jl_sysimg_val_mask) < (b.first & jl_sysimg_val_mask); + }); auto clone_offsets = (int32_t *) malloc(sizeof(int32_t) * clones.size()); auto clone_idxs = (uint32_t *) malloc(sizeof(uint32_t) * clones.size()); for (size_t i = 0; i < clones.size(); i++) { diff --git a/src/processor.h b/src/processor.h index a3ebdf4f8c605..09ee780f2b9c5 100644 --- a/src/processor.h +++ b/src/processor.h @@ -224,6 +224,8 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); // Return the features of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void); // Dump the name and feature set of the host CPU +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits); +// Check if the CPU has native FMA instructions; // For debugging only JL_DLLEXPORT void jl_dump_host_cpu(void); JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char* data); diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index f47d1509f4975..0018d2ec925d9 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1808,6 +1808,22 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ +#ifdef _CPU_AARCH64_ + return jl_true; +#else + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if (bits == 32 && test_nbit(features, Feature::vfp4sp)) + return jl_true; + else if ((bits == 64 || bits == 32) && test_nbit(features, Feature::vfp4)) + return jl_true; + else + return jl_false; +#endif +} + jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 08a4274f44ac8..833cd02b5fdfc 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -172,6 +172,11 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ + return jl_false; // Match behaviour of have_fma in src/llvm-cpufeatures.cpp (assume false) +} + JL_DLLEXPORT void jl_dump_host_cpu(void) { jl_safe_printf("CPU: %s\n", host_cpu_name().c_str()); diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index 73e0992bcf37c..d96e2061ee674 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -4,6 +4,7 @@ // CPUID +#include "julia.h" extern "C" JL_DLLEXPORT void jl_cpuid(int32_t CPUInfo[4], int32_t InfoType) { asm volatile ( @@ -1055,6 +1056,16 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if ((bits == 32 || bits == 64) && (test_nbit(features, Feature::fma) || test_nbit(features, Feature::fma4))) + return jl_true; + else + return jl_false; +} + jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index ed320aa9a6c35..844a5ca8338c7 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -1454,6 +1454,7 @@ un_fintrinsic(trunc_float,trunc_llvm) un_fintrinsic(rint_float,rint_llvm) un_fintrinsic(sqrt_float,sqrt_llvm) un_fintrinsic(sqrt_float,sqrt_llvm_fast) +jl_value_t *jl_cpu_has_fma(int bits); JL_DLLEXPORT jl_value_t *jl_arraylen(jl_value_t *a) { @@ -1463,7 +1464,11 @@ JL_DLLEXPORT jl_value_t *jl_arraylen(jl_value_t *a) JL_DLLEXPORT jl_value_t *jl_have_fma(jl_value_t *typ) { - JL_TYPECHK(have_fma, datatype, typ); - // TODO: run-time feature check? - return jl_false; + JL_TYPECHK(have_fma, datatype, typ); // TODO what about float16/bfloat16? + if (typ == (jl_value_t*)jl_float32_type) + return jl_cpu_has_fma(32); + else if (typ == (jl_value_t*)jl_float64_type) + return jl_cpu_has_fma(64); + else + return jl_false; } diff --git a/src/signals-mach.c b/src/signals-mach.c index 02bb044609ade..6ec8f95570f17 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -384,12 +384,12 @@ static void attach_exception_port(thread_port_t thread, int segv_only) HANDLE_MACH_ERROR("thread_set_exception_ports", ret); } -static int jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) +static int jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) JL_NOTSAFEPOINT { jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; if (ptls2 == NULL) // this thread is not alive return 0; - jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; + jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task); if (ct2 == NULL) // this thread is already dead return 0; @@ -407,18 +407,18 @@ static int jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) return 1; } -static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t **ctx) +int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) { (void)timeout; - static host_thread_state_t state; + host_thread_state_t state; if (!jl_thread_suspend_and_get_state2(tid, &state)) { - *ctx = NULL; - return; + return 0; } - *ctx = (unw_context_t*)&state; + *ctx = *(unw_context_t*)&state; + return 1; } -static void jl_thread_resume(int tid, int sig) +void jl_thread_resume(int tid) { jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; mach_port_t thread = pthread_mach_thread_np(ptls2->system_id); @@ -593,8 +593,15 @@ static void jl_unlock_profile_mach(int dlsymlock, int keymgr_locked) jl_unlock_profile(); } -#define jl_lock_profile() int keymgr_locked = jl_lock_profile_mach(1) -#define jl_unlock_profile() jl_unlock_profile_mach(1, keymgr_locked) +int jl_lock_stackwalk(void) +{ + return jl_lock_profile_mach(1); +} + +void jl_unlock_stackwalk(int lockret) +{ + jl_unlock_profile_mach(1, lockret); +} void *mach_profile_listener(void *arg) { @@ -691,7 +698,7 @@ void *mach_profile_listener(void *arg) bt_data_prof[bt_size_cur++].uintptr = 0; } // We're done! Resume the thread. - jl_thread_resume(i, 0); + jl_thread_resume(i); } jl_unlock_profile_mach(0, keymgr_locked); if (running) { diff --git a/src/signals-unix.c b/src/signals-unix.c index b2056947e2b8a..0d5ad9b1be7c5 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -291,6 +291,18 @@ int exc_reg_is_write_fault(uintptr_t esr) { #include "signals-mach.c" #else +int jl_lock_stackwalk(void) +{ + jl_lock_profile(); + return 0; +} + +void jl_unlock_stackwalk(int lockret) +{ + (void)lockret; + jl_unlock_profile(); +} + #if defined(_OS_LINUX_) && (defined(_CPU_X86_64_) || defined(_CPU_X86_)) int is_write_fault(void *context) { @@ -384,12 +396,12 @@ JL_NO_ASAN static void segv_handler(int sig, siginfo_t *info, void *context) } #if !defined(JL_DISABLE_LIBUNWIND) -static unw_context_t *signal_context; +static bt_context_t *signal_context; pthread_mutex_t in_signal_lock; static pthread_cond_t exit_signal_cond; static pthread_cond_t signal_caught_cond; -static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t **ctx) +int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -399,9 +411,8 @@ static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; if (ct2 == NULL) { // this thread is not alive or already dead - *ctx = NULL; pthread_mutex_unlock(&in_signal_lock); - return; + return 0; } jl_atomic_store_release(&ptls2->signal_request, 1); pthread_kill(ptls2->system_id, SIGUSR2); @@ -410,9 +421,8 @@ static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t if (err == ETIMEDOUT) { sig_atomic_t request = 1; if (jl_atomic_cmpswap(&ptls2->signal_request, &request, 0)) { - *ctx = NULL; pthread_mutex_unlock(&in_signal_lock); - return; + return 0; } // Request is either now 0 (meaning the other thread is waiting for // exit_signal_cond already), @@ -429,15 +439,16 @@ static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t // checking it is 0, and add an acquire barrier for good measure) int request = jl_atomic_load_acquire(&ptls2->signal_request); assert(request == 0); (void) request; - *ctx = signal_context; + jl_atomic_store_release(&ptls2->signal_request, 1); // prepare to resume normally + *ctx = *signal_context; + return 1; } -static void jl_thread_resume(int tid, int sig) +void jl_thread_resume(int tid) { jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; - jl_atomic_store_release(&ptls2->signal_request, sig == -1 ? 3 : 1); pthread_cond_broadcast(&exit_signal_cond); - pthread_cond_wait(&signal_caught_cond, &in_signal_lock); // wait for thread to acknowledge + pthread_cond_wait(&signal_caught_cond, &in_signal_lock); // wait for thread to acknowledge (so that signal_request doesn't get mixed up) // The other thread is waiting to leave exit_signal_cond (verify that here by // checking it is 0, and add an acquire barrier for good measure) int request = jl_atomic_load_acquire(&ptls2->signal_request); @@ -472,14 +483,14 @@ CFI_NORETURN static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) { jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; - unw_context_t *signal_context; + bt_context_t signal_context; // This also makes sure `sleep` is aborted. - jl_thread_suspend_and_get_state(0, 30, &signal_context); - if (signal_context != NULL) { + if (jl_thread_suspend_and_get_state(0, 30, &signal_context)) { thread0_exit_signo = signo; ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0])); - jl_thread_resume(0, -1); // resume with message 3 (call jl_exit_thread0_cb) + jl_atomic_store_release(&ptls2->signal_request, 3); + jl_thread_resume(0); // resume with message 3 (call jl_exit_thread0_cb) } else { // thread 0 is gone? just do the exit ourself @@ -840,11 +851,11 @@ static void *signal_listener(void *arg) int nthreads = jl_atomic_load_acquire(&jl_n_threads); bt_size = 0; #if !defined(JL_DISABLE_LIBUNWIND) - unw_context_t *signal_context; + bt_context_t signal_context; // sample each thread, round-robin style in reverse order // (so that thread zero gets notified last) if (critical || profile) { - jl_lock_profile(); + int lockret = jl_lock_stackwalk(); int *randperm; if (profile) randperm = profile_get_randperm(nthreads); @@ -852,8 +863,7 @@ static void *signal_listener(void *arg) // Stop the threads in the random or reverse round-robin order. int i = profile ? randperm[idx] : idx; // notify thread to stop - jl_thread_suspend_and_get_state(i, 1, &signal_context); - if (signal_context == NULL) + if (!jl_thread_suspend_and_get_state(i, 1, &signal_context)) continue; // do backtrace on thread contexts for critical signals @@ -861,7 +871,7 @@ static void *signal_listener(void *arg) if (critical) { bt_size += rec_backtrace_ctx(bt_data + bt_size, JL_MAX_BT_SIZE / nthreads - 1, - signal_context, NULL); + &signal_context, NULL); bt_data[bt_size++].uintptr = 0; } @@ -883,7 +893,7 @@ static void *signal_listener(void *arg) } else { // Get backtrace data bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, - bt_size_max - bt_size_cur - 1, signal_context, NULL); + bt_size_max - bt_size_cur - 1, &signal_context, NULL); } jl_set_safe_restore(old_buf); @@ -908,9 +918,9 @@ static void *signal_listener(void *arg) } // notify thread to resume - jl_thread_resume(i, sig); + jl_thread_resume(i); } - jl_unlock_profile(); + jl_unlock_stackwalk(lockret); } #ifndef HAVE_MACH if (profile && running) { diff --git a/src/signals-win.c b/src/signals-win.c index 7cd3b02462851..10bd0dec7f480 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -344,6 +344,54 @@ JL_DLLEXPORT void jl_install_sigint_handler(void) static volatile HANDLE hBtThread = 0; +int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx) +{ + (void)timeout; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + if (ptls2 == NULL) // this thread is not alive + return 0; + jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task); + if (ct2 == NULL) // this thread is already dead + return 0; + HANDLE hThread = ptls2->system_id; + if ((DWORD)-1 == SuspendThread(hThread)) + return 0; + assert(sizeof(*ctx) == sizeof(CONTEXT)); + memset(ctx, 0, sizeof(CONTEXT)); + ctx->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext(hThread, ctx)) { + if ((DWORD)-1 == ResumeThread(hThread)) + abort(); + return 0; + } + return 1; +} + +void jl_thread_resume(int tid) +{ + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + HANDLE hThread = ptls2->system_id; + if ((DWORD)-1 == ResumeThread(hThread)) { + fputs("failed to resume main thread! aborting.", stderr); + abort(); + } +} + +int jl_lock_stackwalk(void) +{ + uv_mutex_lock(&jl_in_stackwalk); + jl_lock_profile(); + return 0; +} + +void jl_unlock_stackwalk(int lockret) +{ + (void)lockret; + jl_unlock_profile(); + uv_mutex_unlock(&jl_in_stackwalk); +} + + static DWORD WINAPI profile_bt( LPVOID lparam ) { // Note: illegal to use jl_* functions from this thread except for profiling-specific functions @@ -357,58 +405,45 @@ static DWORD WINAPI profile_bt( LPVOID lparam ) continue; } else { - uv_mutex_lock(&jl_in_stackwalk); - jl_lock_profile(); - if ((DWORD)-1 == SuspendThread(hMainThread)) { - fputs("failed to suspend main thread. aborting profiling.", stderr); - break; - } + // TODO: bring this up to parity with other OS by adding loop over tid here + int lockret = jl_lock_stackwalk(); CONTEXT ctxThread; - memset(&ctxThread, 0, sizeof(CONTEXT)); - ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; - if (!GetThreadContext(hMainThread, &ctxThread)) { - fputs("failed to get context from main thread. aborting profiling.", stderr); + if (!jl_thread_suspend_and_get_state(0, 0, &ctxThread)) { + jl_unlock_stackwalk(lockret); + fputs("failed to suspend main thread. aborting profiling.", stderr); jl_profile_stop_timer(); + break; } - else { - // Get backtrace data - bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, - bt_size_max - bt_size_cur - 1, &ctxThread, NULL); + // Get backtrace data + bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, + bt_size_max - bt_size_cur - 1, &ctxThread, NULL); - jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; // given only profiling hMainThread + jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; // given only profiling hMainThread - // store threadid but add 1 as 0 is preserved to indicate end of block - bt_data_prof[bt_size_cur++].uintptr = ptls->tid + 1; + // store threadid but add 1 as 0 is preserved to indicate end of block + bt_data_prof[bt_size_cur++].uintptr = ptls->tid + 1; - // store task id (never null) - bt_data_prof[bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls->current_task); + // store task id (never null) + bt_data_prof[bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls->current_task); - // store cpu cycle clock - bt_data_prof[bt_size_cur++].uintptr = cycleclock(); + // store cpu cycle clock + bt_data_prof[bt_size_cur++].uintptr = cycleclock(); - // store whether thread is sleeping but add 1 as 0 is preserved to indicate end of block - bt_data_prof[bt_size_cur++].uintptr = jl_atomic_load_relaxed(&ptls->sleep_check_state) + 1; + // store whether thread is sleeping but add 1 as 0 is preserved to indicate end of block + bt_data_prof[bt_size_cur++].uintptr = jl_atomic_load_relaxed(&ptls->sleep_check_state) + 1; - // Mark the end of this block with two 0's - bt_data_prof[bt_size_cur++].uintptr = 0; - bt_data_prof[bt_size_cur++].uintptr = 0; - } - jl_unlock_profile(); - uv_mutex_unlock(&jl_in_stackwalk); - if ((DWORD)-1 == ResumeThread(hMainThread)) { - jl_profile_stop_timer(); - fputs("failed to resume main thread! aborting.", stderr); - jl_gc_debug_critical_error(); - abort(); - } + // Mark the end of this block with two 0's + bt_data_prof[bt_size_cur++].uintptr = 0; + bt_data_prof[bt_size_cur++].uintptr = 0; + jl_unlock_stackwalk(lockret); + jl_thread_resume(0); jl_check_profile_autostop(); } } } - jl_unlock_profile(); uv_mutex_unlock(&jl_in_stackwalk); jl_profile_stop_timer(); - hBtThread = 0; + hBtThread = NULL; return 0; } diff --git a/src/stackwalk.c b/src/stackwalk.c index 18bf4b2126938..dcac2c4501682 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -856,7 +856,7 @@ _os_ptr_munge(uintptr_t ptr) extern bt_context_t *jl_to_bt_context(void *sigctx); -void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT +static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; @@ -865,222 +865,242 @@ void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE, 0); return; } - if (t->copy_stack || !t->started || t->stkbuf == NULL) - return; - int16_t old = -1; - if (!jl_atomic_cmpswap(&t->tid, &old, ptls->tid) && old != ptls->tid) - return; bt_context_t *context = NULL; -#if defined(_OS_WINDOWS_) bt_context_t c; - memset(&c, 0, sizeof(c)); - _JUMP_BUFFER *mctx = (_JUMP_BUFFER*)&t->ctx.ctx.uc_mcontext; + int16_t old = -1; + while (!jl_atomic_cmpswap(&t->tid, &old, ptls->tid) && old != ptls->tid) { + int lockret = jl_lock_stackwalk(); + // if this task is already running somewhere, we need to stop the thread it is running on and query its state + if (!jl_thread_suspend_and_get_state(old, 0, &c)) { + jl_unlock_stackwalk(lockret); + return; + } + jl_unlock_stackwalk(lockret); + if (jl_atomic_load_relaxed(&t->tid) == old) { + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[old]; + if (ptls2->previous_task == t || // we might print the wrong stack here, since we can't know whether we executed the swapcontext yet or not, but it at least avoids trying to access the state inside uc_mcontext which might not be set yet + (ptls2->previous_task == NULL && jl_atomic_load_relaxed(&ptls2->current_task) == t)) { // this case should be always accurate + // use the thread context for the unwind state + context = &c; + } + break; + } + // got the wrong thread stopped, try again + jl_thread_resume(old); + } + if (context == NULL && (!t->copy_stack && t->started && t->stkbuf != NULL)) { + // need to read the context from the task stored state +#if defined(_OS_WINDOWS_) + memset(&c, 0, sizeof(c)); + _JUMP_BUFFER *mctx = (_JUMP_BUFFER*)&t->ctx.ctx.uc_mcontext; #if defined(_CPU_X86_64_) - c.Rbx = mctx->Rbx; - c.Rsp = mctx->Rsp; - c.Rbp = mctx->Rbp; - c.Rsi = mctx->Rsi; - c.Rdi = mctx->Rdi; - c.R12 = mctx->R12; - c.R13 = mctx->R13; - c.R14 = mctx->R14; - c.R15 = mctx->R15; - c.Rip = mctx->Rip; - memcpy(&c.Xmm6, &mctx->Xmm6, 10 * sizeof(mctx->Xmm6)); // Xmm6-Xmm15 + c.Rbx = mctx->Rbx; + c.Rsp = mctx->Rsp; + c.Rbp = mctx->Rbp; + c.Rsi = mctx->Rsi; + c.Rdi = mctx->Rdi; + c.R12 = mctx->R12; + c.R13 = mctx->R13; + c.R14 = mctx->R14; + c.R15 = mctx->R15; + c.Rip = mctx->Rip; + memcpy(&c.Xmm6, &mctx->Xmm6, 10 * sizeof(mctx->Xmm6)); // Xmm6-Xmm15 #else - c.Eip = mctx->Eip; - c.Esp = mctx->Esp; - c.Ebp = mctx->Ebp; + c.Eip = mctx->Eip; + c.Esp = mctx->Esp; + c.Ebp = mctx->Ebp; #endif - context = &c; + context = &c; #elif defined(JL_HAVE_UNW_CONTEXT) - context = &t->ctx.ctx; + context = &t->ctx.ctx; #elif defined(JL_HAVE_UCONTEXT) - context = jl_to_bt_context(&t->ctx.ctx); + context = jl_to_bt_context(&t->ctx.ctx); #elif defined(JL_HAVE_ASM) - bt_context_t c; - memset(&c, 0, sizeof(c)); - #if defined(_OS_LINUX_) && defined(__GLIBC__) - __jmp_buf *mctx = &t->ctx.ctx.uc_mcontext->__jmpbuf; - mcontext_t *mc = &c.uc_mcontext; - #if defined(_CPU_X86_) - // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/i386/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/i386/longjmp.s - mc->gregs[REG_EBX] = (*mctx)[0]; - mc->gregs[REG_ESI] = (*mctx)[1]; - mc->gregs[REG_EDI] = (*mctx)[2]; - mc->gregs[REG_EBP] = (*mctx)[3]; - mc->gregs[REG_ESP] = (*mctx)[4]; - mc->gregs[REG_EIP] = (*mctx)[5]; - // ifdef PTR_DEMANGLE ? - mc->gregs[REG_ESP] = ptr_demangle(mc->gregs[REG_ESP]); - mc->gregs[REG_EIP] = ptr_demangle(mc->gregs[REG_EIP]); - context = &c; - #elif defined(_CPU_X86_64_) - // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/x86_64/setjmp.s - mc->gregs[REG_RBX] = (*mctx)[0]; - mc->gregs[REG_RBP] = (*mctx)[1]; - mc->gregs[REG_R12] = (*mctx)[2]; - mc->gregs[REG_R13] = (*mctx)[3]; - mc->gregs[REG_R14] = (*mctx)[4]; - mc->gregs[REG_R15] = (*mctx)[5]; - mc->gregs[REG_RSP] = (*mctx)[6]; - mc->gregs[REG_RIP] = (*mctx)[7]; - // ifdef PTR_DEMANGLE ? - mc->gregs[REG_RBP] = ptr_demangle(mc->gregs[REG_RBP]); - mc->gregs[REG_RSP] = ptr_demangle(mc->gregs[REG_RSP]); - mc->gregs[REG_RIP] = ptr_demangle(mc->gregs[REG_RIP]); - context = &c; - #elif defined(_CPU_ARM_) - // https://github.com/bminor/glibc/blame/master/sysdeps/arm/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/arm/include/bits/setjmp.h - // https://github.com/bminor/musl/blame/master/src/setjmp/arm/longjmp.S - mc->arm_sp = (*mctx)[0]; - mc->arm_lr = (*mctx)[1]; - mc->arm_r4 = (*mctx)[2]; // aka v1 - mc->arm_r5 = (*mctx)[3]; // aka v2 - mc->arm_r6 = (*mctx)[4]; // aka v3 - mc->arm_r7 = (*mctx)[5]; // aka v4 - mc->arm_r8 = (*mctx)[6]; // aka v5 - mc->arm_r9 = (*mctx)[7]; // aka v6 aka sb - mc->arm_r10 = (*mctx)[8]; // aka v7 aka sl - mc->arm_fp = (*mctx)[10]; // aka v8 aka r11 - // ifdef PTR_DEMANGLE ? - mc->arm_sp = ptr_demangle(mc->arm_sp); - mc->arm_lr = ptr_demangle(mc->arm_lr); - mc->arm_pc = mc->arm_lr; - context = &c; - #elif defined(_CPU_AARCH64_) - // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/__longjmp.S - // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/jmpbuf-offsets.h - // https://github.com/bminor/musl/blame/master/src/setjmp/aarch64/longjmp.s - // https://github.com/libunwind/libunwind/blob/ec171c9ba7ea3abb2a1383cee2988a7abd483a1f/src/aarch64/unwind_i.h#L62 - unw_fpsimd_context_t *mcfp = (unw_fpsimd_context_t*)&mc->__reserved; - mc->regs[19] = (*mctx)[0]; - mc->regs[20] = (*mctx)[1]; - mc->regs[21] = (*mctx)[2]; - mc->regs[22] = (*mctx)[3]; - mc->regs[23] = (*mctx)[4]; - mc->regs[24] = (*mctx)[5]; - mc->regs[25] = (*mctx)[6]; - mc->regs[26] = (*mctx)[7]; - mc->regs[27] = (*mctx)[8]; - mc->regs[28] = (*mctx)[9]; - mc->regs[29] = (*mctx)[10]; // aka fp - mc->regs[30] = (*mctx)[11]; // aka lr - // Yes, they did skip 12 why writing the code originally; and, no, I do not know why. - mc->sp = (*mctx)[13]; - mcfp->vregs[7] = (*mctx)[14]; // aka d8 - mcfp->vregs[8] = (*mctx)[15]; // aka d9 - mcfp->vregs[9] = (*mctx)[16]; // aka d10 - mcfp->vregs[10] = (*mctx)[17]; // aka d11 - mcfp->vregs[11] = (*mctx)[18]; // aka d12 - mcfp->vregs[12] = (*mctx)[19]; // aka d13 - mcfp->vregs[13] = (*mctx)[20]; // aka d14 - mcfp->vregs[14] = (*mctx)[21]; // aka d15 - // ifdef PTR_DEMANGLE ? - mc->sp = ptr_demangle(mc->sp); - mc->regs[30] = ptr_demangle(mc->regs[30]); - mc->pc = mc->regs[30]; - context = &c; - #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown linux") - (void)mc; - (void)c; - (void)mctx; - #endif - #elif defined(_OS_DARWIN_) - sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; - #if defined(_CPU_X86_64_) - // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s - x86_thread_state64_t *mc = (x86_thread_state64_t*)&c; - mc->__rbx = ((uint64_t*)mctx)[0]; - mc->__rbp = ((uint64_t*)mctx)[1]; - mc->__rsp = ((uint64_t*)mctx)[2]; - mc->__r12 = ((uint64_t*)mctx)[3]; - mc->__r13 = ((uint64_t*)mctx)[4]; - mc->__r14 = ((uint64_t*)mctx)[5]; - mc->__r15 = ((uint64_t*)mctx)[6]; - mc->__rip = ((uint64_t*)mctx)[7]; - // added in libsystem_plaform 177.200.16 (macOS Mojave 10.14.3) - // prior to that _os_ptr_munge_token was (hopefully) typically 0, - // so x ^ 0 == x and this is a no-op - mc->__rbp = _OS_PTR_UNMUNGE(mc->__rbp); - mc->__rsp = _OS_PTR_UNMUNGE(mc->__rsp); - mc->__rip = _OS_PTR_UNMUNGE(mc->__rip); - context = &c; - #elif defined(_CPU_AARCH64_) - // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/arm64/setjmp.s - // https://github.com/apple/darwin-xnu/blob/main/osfmk/mach/arm/_structs.h - // https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/libunwind/src/Registers.hpp -> Registers_arm64 - arm_thread_state64_t *mc = (arm_thread_state64_t*)&c; - mc->__x[19] = ((uint64_t*)mctx)[0]; - mc->__x[20] = ((uint64_t*)mctx)[1]; - mc->__x[21] = ((uint64_t*)mctx)[2]; - mc->__x[22] = ((uint64_t*)mctx)[3]; - mc->__x[23] = ((uint64_t*)mctx)[4]; - mc->__x[24] = ((uint64_t*)mctx)[5]; - mc->__x[25] = ((uint64_t*)mctx)[6]; - mc->__x[26] = ((uint64_t*)mctx)[7]; - mc->__x[27] = ((uint64_t*)mctx)[8]; - mc->__x[28] = ((uint64_t*)mctx)[9]; - mc->__x[10] = ((uint64_t*)mctx)[10]; - mc->__x[11] = ((uint64_t*)mctx)[11]; - mc->__x[12] = ((uint64_t*)mctx)[12]; - // 13 is reserved/unused - double *mcfp = (double*)&mc[1]; - mcfp[7] = ((uint64_t*)mctx)[14]; // aka d8 - mcfp[8] = ((uint64_t*)mctx)[15]; // aka d9 - mcfp[9] = ((uint64_t*)mctx)[16]; // aka d10 - mcfp[10] = ((uint64_t*)mctx)[17]; // aka d11 - mcfp[11] = ((uint64_t*)mctx)[18]; // aka d12 - mcfp[12] = ((uint64_t*)mctx)[19]; // aka d13 - mcfp[13] = ((uint64_t*)mctx)[20]; // aka d14 - mcfp[14] = ((uint64_t*)mctx)[21]; // aka d15 - mc->__fp = _OS_PTR_UNMUNGE(mc->__x[10]); - mc->__lr = _OS_PTR_UNMUNGE(mc->__x[11]); - mc->__x[12] = _OS_PTR_UNMUNGE(mc->__x[12]); - mc->__sp = mc->__x[12]; - // libunwind is broken for signed-pointers, but perhaps best not to leave the signed pointer lying around either - mc->__pc = ptrauth_strip(mc->__lr, 0); - mc->__pad = 0; // aka __ra_sign_state = not signed - context = &c; - #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown darwin") - (void)mctx; - (void)c; - #endif - #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) - sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; - mcontext_t *mc = &c.uc_mcontext; - // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S - mc->mc_rip = ((long*)mctx)[0]; - mc->mc_rbx = ((long*)mctx)[1]; - mc->mc_rsp = ((long*)mctx)[2]; - mc->mc_rbp = ((long*)mctx)[3]; - mc->mc_r12 = ((long*)mctx)[4]; - mc->mc_r13 = ((long*)mctx)[5]; - mc->mc_r14 = ((long*)mctx)[6]; - mc->mc_r15 = ((long*)mctx)[7]; - context = &c; - #else - #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown system") - (void)c; - #endif + memset(&c, 0, sizeof(c)); + #if defined(_OS_LINUX_) && defined(__GLIBC__) + __jmp_buf *mctx = &t->ctx.ctx.uc_mcontext->__jmpbuf; + mcontext_t *mc = &c.uc_mcontext; + #if defined(_CPU_X86_) + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/i386/longjmp.s + mc->gregs[REG_EBX] = (*mctx)[0]; + mc->gregs[REG_ESI] = (*mctx)[1]; + mc->gregs[REG_EDI] = (*mctx)[2]; + mc->gregs[REG_EBP] = (*mctx)[3]; + mc->gregs[REG_ESP] = (*mctx)[4]; + mc->gregs[REG_EIP] = (*mctx)[5]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_ESP] = ptr_demangle(mc->gregs[REG_ESP]); + mc->gregs[REG_EIP] = ptr_demangle(mc->gregs[REG_EIP]); + context = &c; + #elif defined(_CPU_X86_64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/x86_64/setjmp.s + mc->gregs[REG_RBX] = (*mctx)[0]; + mc->gregs[REG_RBP] = (*mctx)[1]; + mc->gregs[REG_R12] = (*mctx)[2]; + mc->gregs[REG_R13] = (*mctx)[3]; + mc->gregs[REG_R14] = (*mctx)[4]; + mc->gregs[REG_R15] = (*mctx)[5]; + mc->gregs[REG_RSP] = (*mctx)[6]; + mc->gregs[REG_RIP] = (*mctx)[7]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_RBP] = ptr_demangle(mc->gregs[REG_RBP]); + mc->gregs[REG_RSP] = ptr_demangle(mc->gregs[REG_RSP]); + mc->gregs[REG_RIP] = ptr_demangle(mc->gregs[REG_RIP]); + context = &c; + #elif defined(_CPU_ARM_) + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/include/bits/setjmp.h + // https://github.com/bminor/musl/blame/master/src/setjmp/arm/longjmp.S + mc->arm_sp = (*mctx)[0]; + mc->arm_lr = (*mctx)[1]; + mc->arm_r4 = (*mctx)[2]; // aka v1 + mc->arm_r5 = (*mctx)[3]; // aka v2 + mc->arm_r6 = (*mctx)[4]; // aka v3 + mc->arm_r7 = (*mctx)[5]; // aka v4 + mc->arm_r8 = (*mctx)[6]; // aka v5 + mc->arm_r9 = (*mctx)[7]; // aka v6 aka sb + mc->arm_r10 = (*mctx)[8]; // aka v7 aka sl + mc->arm_fp = (*mctx)[10]; // aka v8 aka r11 + // ifdef PTR_DEMANGLE ? + mc->arm_sp = ptr_demangle(mc->arm_sp); + mc->arm_lr = ptr_demangle(mc->arm_lr); + mc->arm_pc = mc->arm_lr; + context = &c; + #elif defined(_CPU_AARCH64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/aarch64/longjmp.s + // https://github.com/libunwind/libunwind/blob/ec171c9ba7ea3abb2a1383cee2988a7abd483a1f/src/aarch64/unwind_i.h#L62 + unw_fpsimd_context_t *mcfp = (unw_fpsimd_context_t*)&mc->__reserved; + mc->regs[19] = (*mctx)[0]; + mc->regs[20] = (*mctx)[1]; + mc->regs[21] = (*mctx)[2]; + mc->regs[22] = (*mctx)[3]; + mc->regs[23] = (*mctx)[4]; + mc->regs[24] = (*mctx)[5]; + mc->regs[25] = (*mctx)[6]; + mc->regs[26] = (*mctx)[7]; + mc->regs[27] = (*mctx)[8]; + mc->regs[28] = (*mctx)[9]; + mc->regs[29] = (*mctx)[10]; // aka fp + mc->regs[30] = (*mctx)[11]; // aka lr + // Yes, they did skip 12 why writing the code originally; and, no, I do not know why. + mc->sp = (*mctx)[13]; + mcfp->vregs[7] = (*mctx)[14]; // aka d8 + mcfp->vregs[8] = (*mctx)[15]; // aka d9 + mcfp->vregs[9] = (*mctx)[16]; // aka d10 + mcfp->vregs[10] = (*mctx)[17]; // aka d11 + mcfp->vregs[11] = (*mctx)[18]; // aka d12 + mcfp->vregs[12] = (*mctx)[19]; // aka d13 + mcfp->vregs[13] = (*mctx)[20]; // aka d14 + mcfp->vregs[14] = (*mctx)[21]; // aka d15 + // ifdef PTR_DEMANGLE ? + mc->sp = ptr_demangle(mc->sp); + mc->regs[30] = ptr_demangle(mc->regs[30]); + mc->pc = mc->regs[30]; + context = &c; + #else + #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown linux") + (void)mc; + (void)c; + (void)mctx; + #endif + #elif defined(_OS_DARWIN_) + sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + #if defined(_CPU_X86_64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s + x86_thread_state64_t *mc = (x86_thread_state64_t*)&c; + mc->__rbx = ((uint64_t*)mctx)[0]; + mc->__rbp = ((uint64_t*)mctx)[1]; + mc->__rsp = ((uint64_t*)mctx)[2]; + mc->__r12 = ((uint64_t*)mctx)[3]; + mc->__r13 = ((uint64_t*)mctx)[4]; + mc->__r14 = ((uint64_t*)mctx)[5]; + mc->__r15 = ((uint64_t*)mctx)[6]; + mc->__rip = ((uint64_t*)mctx)[7]; + // added in libsystem_plaform 177.200.16 (macOS Mojave 10.14.3) + // prior to that _os_ptr_munge_token was (hopefully) typically 0, + // so x ^ 0 == x and this is a no-op + mc->__rbp = _OS_PTR_UNMUNGE(mc->__rbp); + mc->__rsp = _OS_PTR_UNMUNGE(mc->__rsp); + mc->__rip = _OS_PTR_UNMUNGE(mc->__rip); + context = &c; + #elif defined(_CPU_AARCH64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/arm64/setjmp.s + // https://github.com/apple/darwin-xnu/blob/main/osfmk/mach/arm/_structs.h + // https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/libunwind/src/Registers.hpp -> Registers_arm64 + arm_thread_state64_t *mc = (arm_thread_state64_t*)&c; + mc->__x[19] = ((uint64_t*)mctx)[0]; + mc->__x[20] = ((uint64_t*)mctx)[1]; + mc->__x[21] = ((uint64_t*)mctx)[2]; + mc->__x[22] = ((uint64_t*)mctx)[3]; + mc->__x[23] = ((uint64_t*)mctx)[4]; + mc->__x[24] = ((uint64_t*)mctx)[5]; + mc->__x[25] = ((uint64_t*)mctx)[6]; + mc->__x[26] = ((uint64_t*)mctx)[7]; + mc->__x[27] = ((uint64_t*)mctx)[8]; + mc->__x[28] = ((uint64_t*)mctx)[9]; + mc->__x[10] = ((uint64_t*)mctx)[10]; + mc->__x[11] = ((uint64_t*)mctx)[11]; + mc->__x[12] = ((uint64_t*)mctx)[12]; + // 13 is reserved/unused + double *mcfp = (double*)&mc[1]; + mcfp[7] = ((uint64_t*)mctx)[14]; // aka d8 + mcfp[8] = ((uint64_t*)mctx)[15]; // aka d9 + mcfp[9] = ((uint64_t*)mctx)[16]; // aka d10 + mcfp[10] = ((uint64_t*)mctx)[17]; // aka d11 + mcfp[11] = ((uint64_t*)mctx)[18]; // aka d12 + mcfp[12] = ((uint64_t*)mctx)[19]; // aka d13 + mcfp[13] = ((uint64_t*)mctx)[20]; // aka d14 + mcfp[14] = ((uint64_t*)mctx)[21]; // aka d15 + mc->__fp = _OS_PTR_UNMUNGE(mc->__x[10]); + mc->__lr = _OS_PTR_UNMUNGE(mc->__x[11]); + mc->__x[12] = _OS_PTR_UNMUNGE(mc->__x[12]); + mc->__sp = mc->__x[12]; + // libunwind is broken for signed-pointers, but perhaps best not to leave the signed pointer lying around either + mc->__pc = ptrauth_strip(mc->__lr, 0); + mc->__pad = 0; // aka __ra_sign_state = not signed + context = &c; + #else + #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown darwin") + (void)mctx; + (void)c; + #endif + #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) + sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + mcontext_t *mc = &c.uc_mcontext; + // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S + mc->mc_rip = ((long*)mctx)[0]; + mc->mc_rbx = ((long*)mctx)[1]; + mc->mc_rsp = ((long*)mctx)[2]; + mc->mc_rbp = ((long*)mctx)[3]; + mc->mc_r12 = ((long*)mctx)[4]; + mc->mc_r13 = ((long*)mctx)[5]; + mc->mc_r14 = ((long*)mctx)[6]; + mc->mc_r15 = ((long*)mctx)[7]; + context = &c; + #else + #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown system") + (void)c; + #endif #elif defined(JL_HAVE_ASYNCIFY) - #pragma message("jl_rec_backtrace not defined for ASYNCIFY") + #pragma message("jl_rec_backtrace not defined for ASYNCIFY") #elif defined(JL_HAVE_SIGALTSTACK) - #pragma message("jl_rec_backtrace not defined for SIGALTSTACK") + #pragma message("jl_rec_backtrace not defined for SIGALTSTACK") #else - #pragma message("jl_rec_backtrace not defined for unknown task system") + #pragma message("jl_rec_backtrace not defined for unknown task system") #endif + } if (context) - ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, context, t->gcstack); + ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, context, t->gcstack); if (old == -1) jl_atomic_store_relaxed(&t->tid, old); + else if (old != ptls->tid) + jl_thread_resume(old); } //-------------------------------------------------- @@ -1107,7 +1127,7 @@ JL_DLLEXPORT void jlbacktrace(void) JL_NOTSAFEPOINT } } -// Print backtrace for specified task +// Print backtrace for specified task to jl_safe_printf stderr JL_DLLEXPORT void jlbacktracet(jl_task_t *t) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; @@ -1125,34 +1145,49 @@ JL_DLLEXPORT void jl_print_backtrace(void) JL_NOTSAFEPOINT jlbacktrace(); } -// Print backtraces for all live tasks, for all threads. -// WARNING: this is dangerous and can crash if used outside of gdb, if -// all of Julia's threads are not stopped! +// Print backtraces for all live tasks, for all threads, to jl_safe_printf stderr JL_DLLEXPORT void jl_print_task_backtraces(int show_done) JL_NOTSAFEPOINT { size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); for (size_t i = 0; i < nthreads; i++) { jl_ptls_t ptls2 = allstates[i]; - arraylist_t *live_tasks = &ptls2->heap.live_tasks; - size_t n = live_tasks->len; + if (ptls2 == NULL) { + continue; + } + small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; + size_t n = mtarraylist_length(live_tasks); + int t_state = JL_TASK_STATE_DONE; + jl_task_t *t = ptls2->root_task; + if (t != NULL) + t_state = jl_atomic_load_relaxed(&t->_state); jl_safe_printf("==== Thread %d created %zu live tasks\n", - ptls2->tid + 1, n + 1); - jl_safe_printf(" ---- Root task (%p)\n", ptls2->root_task); - jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", - ptls2->root_task->sticky, ptls2->root_task->started, - jl_atomic_load_relaxed(&ptls2->root_task->_state), - jl_atomic_load_relaxed(&ptls2->root_task->tid) + 1); - jlbacktracet(ptls2->root_task); + ptls2->tid + 1, n + (t_state != JL_TASK_STATE_DONE)); + if (show_done || t_state != JL_TASK_STATE_DONE) { + jl_safe_printf(" ---- Root task (%p)\n", ptls2->root_task); + if (t != NULL) { + jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", + t->sticky, t->started, t_state, + jl_atomic_load_relaxed(&t->tid) + 1); + if (t->stkbuf != NULL) { + jlbacktracet(t); + } + else { + jl_safe_printf(" no stack\n"); + } + } + jl_safe_printf(" ---- End root task\n"); + } - void **lst = live_tasks->items; - for (size_t j = 0; j < live_tasks->len; j++) { - jl_task_t *t = (jl_task_t *)lst[j]; + for (size_t j = 0; j < n; j++) { + jl_task_t *t = (jl_task_t*)mtarraylist_get(live_tasks, j); + if (t == NULL) + continue; int t_state = jl_atomic_load_relaxed(&t->_state); - if (!show_done && t_state == JL_TASK_STATE_DONE) { + if (!show_done && t_state == JL_TASK_STATE_DONE) continue; - } jl_safe_printf(" ---- Task %zu (%p)\n", j + 1, t); + // n.b. this information might not be consistent with the stack printing after it, since it could start running or change tid, etc. jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", t->sticky, t->started, t_state, jl_atomic_load_relaxed(&t->tid) + 1); diff --git a/src/staticdata.c b/src/staticdata.c index a974d98a39835..36961b58f375a 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -99,7 +99,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 158 +#define NUM_TAGS 159 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -224,6 +224,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_undefref_exception); INSERT_TAG(jl_readonlymemory_exception); INSERT_TAG(jl_atomicerror_type); + INSERT_TAG(jl_precompilable_error); // other special values INSERT_TAG(jl_emptysvec); @@ -3371,7 +3372,7 @@ static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_ } // TODO?: refactor to make it easier to create the "package inspector" -static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) { JL_TIMING(LOAD_IMAGE, LOAD_Pkgimg); jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, pkgname); @@ -3426,7 +3427,7 @@ static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *im size_t world = jl_atomic_load_acquire(&jl_world_counter); jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)new_specializations, world); // restore external backedges (needs to be last) // reinit ccallables - jl_reinit_ccallable(&ccallable_list, base, NULL); + jl_reinit_ccallable(&ccallable_list, base, pkgimage_handle); arraylist_free(&ccallable_list); if (completeinfo) { @@ -3457,11 +3458,11 @@ static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, uin jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) { ios_t f; ios_static_buffer(&f, (char*)buf, sz); - jl_value_t *ret = jl_restore_package_image_from_stream(&f, image, depmods, completeinfo, pkgname, needs_permalloc); + jl_value_t *ret = jl_restore_package_image_from_stream(pkgimage_handle, &f, image, depmods, completeinfo, pkgname, needs_permalloc); ios_close(&f); return ret; } @@ -3474,7 +3475,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *d "Cache file \"%s\" not found.\n", fname); } jl_image_t pkgimage = {}; - jl_value_t *ret = jl_restore_package_image_from_stream(&f, &pkgimage, depmods, completeinfo, pkgname, true); + jl_value_t *ret = jl_restore_package_image_from_stream(NULL, &f, &pkgimage, depmods, completeinfo, pkgname, true); ios_close(&f); return ret; } @@ -3545,7 +3546,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j jl_image_t pkgimage = jl_init_processor_pkgimg(pkgimg_handle); - jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, false); + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, false); return mod; } diff --git a/src/threading.c b/src/threading.c index 4faa8a0a2dc46..b82e36f66d994 100644 --- a/src/threading.c +++ b/src/threading.c @@ -314,6 +314,8 @@ static uv_mutex_t tls_lock; // controls write-access to these variables: _Atomic(jl_ptls_t*) jl_all_tls_states JL_GLOBALLY_ROOTED; int jl_all_tls_states_size; static uv_cond_t cond; +// concurrent reads are permitted, using the same pattern as mtsmall_arraylist +// it is implemented separately because the API of direct jl_all_tls_states use is already widely prevalent // return calling thread's ID JL_DLLEXPORT int16_t jl_threadid(void) @@ -347,7 +349,7 @@ jl_ptls_t jl_init_threadtls(int16_t tid) #ifndef _OS_WINDOWS_ pthread_setspecific(jl_task_exit_key, (void*)ptls); #endif - ptls->system_id = (jl_thread_t)(uintptr_t)uv_thread_self(); + ptls->system_id = uv_thread_self(); ptls->rngseed = jl_rand(); if (tid == 0) ptls->disable_gc = 1; @@ -382,10 +384,10 @@ jl_ptls_t jl_init_threadtls(int16_t tid) uv_cond_init(&ptls->wake_signal); uv_mutex_lock(&tls_lock); - jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); if (tid == -1) tid = jl_atomic_load_relaxed(&jl_n_threads); ptls->tid = tid; + jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); if (jl_all_tls_states_size <= tid) { int i, newsize = jl_all_tls_states_size + tid + 2; jl_ptls_t *newpptls = (jl_ptls_t*)calloc(newsize, sizeof(jl_ptls_t)); @@ -655,6 +657,7 @@ void jl_init_threading(void) } } + int cpu = jl_cpu_threads(); jl_n_markthreads = jl_options.nmarkthreads - 1; jl_n_sweepthreads = jl_options.nsweepthreads; if (jl_n_markthreads == -1) { // --gcthreads not specified @@ -683,8 +686,20 @@ void jl_init_threading(void) else { jl_n_markthreads = (nthreads / 2) - 1; } + // if `--gcthreads` or ENV[NUM_GCTHREADS_NAME] was not specified, + // cap the number of threads that may run the mark phase to + // the number of CPU cores + if (jl_n_markthreads + 1 >= cpu) { + jl_n_markthreads = cpu - 1; + } } } + // warn the user if they try to run with a number + // of GC threads which is larger than the number + // of physical cores + if (jl_n_markthreads + 1 > cpu) { + jl_safe_printf("WARNING: running Julia with %d GC threads on %d CPU cores\n", jl_n_markthreads + 1, cpu); + } int16_t ngcthreads = jl_n_markthreads + jl_n_sweepthreads; jl_all_tls_states_size = nthreads + nthreadsi + ngcthreads; diff --git a/stdlib/Artifacts/src/Artifacts.jl b/stdlib/Artifacts/src/Artifacts.jl index 70593bfadae05..5daf994bfbf10 100644 --- a/stdlib/Artifacts/src/Artifacts.jl +++ b/stdlib/Artifacts/src/Artifacts.jl @@ -548,7 +548,7 @@ function _artifact_str(__module__, artifacts_toml, name, path_tail, artifact_dic if nameof(lazyartifacts) in (:Pkg, :Artifacts) Base.depwarn("using Pkg instead of using LazyArtifacts is deprecated", :var"@artifact_str", force=true) end - return jointail(lazyartifacts.ensure_artifact_installed(string(name), artifacts_toml; platform), path_tail) + return jointail(lazyartifacts.ensure_artifact_installed(string(name), meta, artifacts_toml; platform), path_tail) end error("Artifact $(repr(name)) is a lazy artifact; package developers must call `using LazyArtifacts` in $(__module__) before using lazy artifacts.") end diff --git a/stdlib/Dates/src/arithmetic.jl b/stdlib/Dates/src/arithmetic.jl index b379747c5f9e9..83a2873b43409 100644 --- a/stdlib/Dates/src/arithmetic.jl +++ b/stdlib/Dates/src/arithmetic.jl @@ -7,7 +7,7 @@ # TimeType arithmetic (+)(x::TimeType) = x (-)(x::T, y::T) where {T<:TimeType} = x.instant - y.instant -(-)(x::DateTime, y::DateTime) = x.instant - y.instant +(-)(x::T, y::T) where {T<:AbstractDateTime} = x.instant - y.instant (-)(x::AbstractDateTime, y::AbstractDateTime) = -(promote(x, y)...) # Date-Time arithmetic diff --git a/stdlib/Dates/test/arithmetic.jl b/stdlib/Dates/test/arithmetic.jl index d266526496c45..e3ed6ace57c69 100644 --- a/stdlib/Dates/test/arithmetic.jl +++ b/stdlib/Dates/test/arithmetic.jl @@ -14,10 +14,15 @@ end struct MonthlyDate <: TimeType instant::Dates.UTInstant{Month} end +struct OtherTime <: Dates.AbstractDateTime + instant::Dates.UTInstant{Nanosecond} +end @testset "TimeType arithmetic" begin @test_throws MethodError DateTime(2023, 5, 2) - Date(2023, 5, 1) # check that - between two same-type TimeTypes works by default @test MonthlyDate(Dates.UTInstant(Month(10))) - MonthlyDate(Dates.UTInstant(Month(1))) == Month(9) + # ... and between two same-type AbstractDateTimes + @test OtherTime(Dates.UTInstant(Nanosecond(2))) - OtherTime(Dates.UTInstant(Nanosecond(1))) == Nanosecond(1) end @testset "Wrapping arithmetic for Months" begin diff --git a/stdlib/LinearAlgebra/src/LinearAlgebra.jl b/stdlib/LinearAlgebra/src/LinearAlgebra.jl index 85ba1d2770ba7..494572b1e8dd8 100644 --- a/stdlib/LinearAlgebra/src/LinearAlgebra.jl +++ b/stdlib/LinearAlgebra/src/LinearAlgebra.jl @@ -619,7 +619,9 @@ function peakflops(n::Integer=4096; eltype::DataType=Float64, ntrials::Integer=3 if parallel let Distributed = Base.require(Base.PkgId( Base.UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) - return sum(Distributed.pmap(peakflops, fill(n, Distributed.nworkers()))) + nworkers = @invokelatest Distributed.nworkers() + results = @invokelatest Distributed.pmap(peakflops, fill(n, nworkers)) + return sum(results) end else return 2*Float64(n)^3 / minimum(t) diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index 7c2d80c35fe19..b43eb592268ab 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = release-1.10 -PKG_SHA1 = e63c601bd3a00a8e6062d926673337c8a659542d +PKG_SHA1 = 563a3387acddad6c427b2f675aa70531c4324cc2 PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index debe568b25f64..b94ea3da2890b 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -243,8 +243,21 @@ const sorted_keyvals = ["false", "true"] complete_keyval(s::Union{String,SubString{String}}) = complete_from_list(KeyvalCompletion, sorted_keyvals, s) -function complete_path(path::AbstractString, pos::Int; - use_envpath=false, shell_escape=false, +function do_raw_escape(s) + # escape_raw_string with delim='`' and ignoring the rule for the ending \ + return replace(s, r"(\\+)`" => s"\1\\`") +end +function do_shell_escape(s) + return Base.shell_escape_posixly(s) +end +function do_string_escape(s) + return escape_string(s, ('\"','$')) +end + +function complete_path(path::AbstractString; + use_envpath=false, + shell_escape=false, + raw_escape=false, string_escape=false) @assert !(shell_escape && string_escape) if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) @@ -257,50 +270,49 @@ function complete_path(path::AbstractString, pos::Int; else dir, prefix = splitdir(path) end - local files - try + files = try if isempty(dir) - files = readdir() + readdir() elseif isdir(dir) - files = readdir(dir) + readdir(dir) else - return Completion[], 0:-1, false + return Completion[], dir, false end - catch - return Completion[], 0:-1, false + catch ex + ex isa Base.IOError || rethrow() + return Completion[], dir, false end matches = Set{String}() for file in files if startswith(file, prefix) p = joinpath(dir, file) - is_dir = try isdir(p) catch; false end - push!(matches, is_dir ? joinpath(file, "") : file) + is_dir = try isdir(p) catch ex; ex isa Base.IOError ? false : rethrow() end + push!(matches, is_dir ? file * "/" : file) end end - if use_envpath && length(dir) == 0 + if use_envpath && isempty(dir) # Look for files in PATH as well - local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":") + pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":") for pathdir in pathdirs - local actualpath - try - actualpath = realpath(pathdir) - catch + actualpath = try + realpath(pathdir) + catch ex + ex isa Base.IOError || rethrow() # Bash doesn't expect every folder in PATH to exist, so neither shall we continue end - if actualpath != pathdir && in(actualpath,pathdirs) + if actualpath != pathdir && in(actualpath, pathdirs) # Remove paths which (after resolving links) are in the env path twice. # Many distros eg. point /bin to /usr/bin but have both in the env path. continue end - local filesinpath - try - filesinpath = readdir(pathdir) + filesinpath = try + readdir(pathdir) catch e # Bash allows dirs in PATH that can't be read, so we should as well. if isa(e, Base.IOError) || isa(e, Base.ArgumentError) @@ -321,18 +333,38 @@ function complete_path(path::AbstractString, pos::Int; end end - function do_escape(s) - return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") : - string_escape ? escape_string(s, ('\"','$')) : - s - end + matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches) + matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches) + matches = Completion[PathCompletion(s) for s in matches] + return matches, dir, !isempty(matches) +end - matchList = Completion[PathCompletion(do_escape(s)) for s in matches] - startpos = pos - lastindex(do_escape(prefix)) + 1 - # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`, - # hence we need to add one to get the first index. This is also correct when considering - # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`. - return matchList, startpos:pos, !isempty(matchList) +function complete_path(path::AbstractString, + pos::Int; + use_envpath=false, + shell_escape=false, + string_escape=false) + ## TODO: enable this depwarn once Pkg is fixed + #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path) + paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape) + if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) + # if the path is just "~", don't consider the expanded username as a prefix + if path == "~" + dir, prefix = homedir(), "" + else + dir, prefix = splitdir(homedir() * path[2:end]) + end + else + dir, prefix = splitdir(path) + end + startpos = pos - lastindex(prefix) + 1 + Sys.iswindows() && map!(paths, paths) do c::PathCompletion + # emulation for unnecessarily complicated return value, since / is a + # perfectly acceptable path character which does not require quoting + # but is required by Pkg's awkward parser handling + return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c + end + return paths, startpos:pos, success end function complete_expanduser(path::AbstractString, r) @@ -784,10 +816,11 @@ function afterusing(string::String, startpos::Int) return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end]) end -function close_path_completion(str, startpos, r, paths, pos) +function close_path_completion(dir, paths, str, pos) length(paths) == 1 || return false # Only close if there's a single choice... - _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path - path = expanduser(unescape_string(replace(_path, "\\\$"=>"\$", "\\\""=>"\""))) + path = (paths[1]::PathCompletion).path + path = unescape_string(replace(path, "\\\$"=>"\$")) + path = joinpath(dir, path) # ...except if it's a directory... try isdir(path) @@ -1008,6 +1041,11 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff if isinfix ex = ex.args[end] end + elseif isexpr(ex, :macrocall) && length(ex.args) > 1 + # allow symbol completions within potentially incomplete macrocalls + if s[end] ≠ '`' && s[end] ≠ ')' + ex = ex.args[end] + end end end append!(suggestions, complete_symbol(ex, name, ffunc, context_module)) @@ -1075,42 +1113,80 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif return complete_identifiers!(Completion[], ffunc, context_module, string, string[startpos:pos], pos, dotpos, startpos) elseif inc_tag === :cmd - m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) - startpos = nextind(partial, reverseind(partial, m.offset)) - r = startpos:pos - - # This expansion with "\\ "=>' ' replacement and shell_escape=true - # assumes the path isn't further quoted within the cmd backticks. - expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r) - expanded[3] && return expanded # If user expansion available, return it - - paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos, - shell_escape=true) - - return sort!(paths, by=p->p.path), r, success + # TODO: should this call shell_completions instead of partially reimplementing it? + let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + scs::String = string[r] + + expanded = complete_expanduser(scs, r) + expanded[3] && return expanded # If user expansion available, return it + + path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many + # This expansion with "\\ "=>' ' replacement and shell_escape=true + # assumes the path isn't further quoted within the cmd backticks. + path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly) + paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true) + + if success && !isempty(dir) + let dir = do_raw_escape(do_shell_escape(dir)) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + if endswith(dir, "/") && startswith(scs, dir) + r = (startpos + sizeof(dir)):pos + elseif startswith(scs, dir * "/") + r = nextind(string, startpos + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end + return sort!(paths, by=p->p.path), r, success + end elseif inc_tag === :string # Find first non-escaped quote - m = match(r"\"(?!\\)", reverse(partial)) - startpos = nextind(partial, reverseind(partial, m.offset)) - r = startpos:pos + let m = match(r"\"(?!\\)", reverse(partial)) + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + scs::String = string[r] + + expanded = complete_expanduser(scs, r) + expanded[3] && return expanded # If user expansion available, return it + + path = try + unescape_string(replace(scs, "\\\$"=>"\$")) + catch ex + ex isa ArgumentError || rethrow() + nothing + end + if !isnothing(path) + paths, dir, success = complete_path(path::String, string_escape=true) - expanded = complete_expanduser(string[r], r) - expanded[3] && return expanded # If user expansion available, return it + if close_path_completion(dir, paths, path, pos) + paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + end - path_prefix = try - unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\"")) - catch - nothing - end - if !isnothing(path_prefix) - paths, r, success = complete_path(path_prefix, pos, string_escape=true) + if success && !isempty(dir) + let dir = do_string_escape(dir) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + if endswith(dir, "/") && startswith(scs, dir) + r = (startpos + sizeof(dir)):pos + elseif startswith(scs, dir * "/") + r = nextind(string, startpos + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end - if close_path_completion(string, startpos, r, paths, pos) - paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + # Fallthrough allowed so that Latex symbols can be completed in strings + success && return sort!(paths, by=p->p.path), r, success end - - # Fallthrough allowed so that Latex symbols can be completed in strings - success && return sort!(paths, by=p->p.path), r, success end end @@ -1196,35 +1272,58 @@ end function shell_completions(string, pos) # First parse everything up to the current position scs = string[1:pos] - local args, last_parse - try - args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}} - catch + args, last_arg_start = try + Base.shell_parse(scs, true)::Tuple{Expr,Int} + catch ex + ex isa ArgumentError || ex isa ErrorException || rethrow() return Completion[], 0:-1, false end ex = args.args[end]::Expr # Now look at the last thing we parsed isempty(ex.args) && return Completion[], 0:-1, false - arg = ex.args[end] - if all(s -> isa(s, AbstractString), ex.args) - arg = arg::AbstractString - # Treat this as a path - - # As Base.shell_parse throws away trailing spaces (unless they are escaped), - # we need to special case here. - # If the last char was a space, but shell_parse ignored it search on "". - ignore_last_word = arg != " " && scs[end] == ' ' - prefix = ignore_last_word ? "" : join(ex.args) + lastarg = ex.args[end] + # As Base.shell_parse throws away trailing spaces (unless they are escaped), + # we need to special case here. + # If the last char was a space, but shell_parse ignored it search on "". + if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error) + partial = string[last_arg_start:pos] + ret, range = completions(partial, lastindex(partial)) + range = range .+ (last_arg_start - 1) + return ret, range, true + elseif endswith(scs, ' ') && !endswith(scs, "\\ ") + r = pos+1:pos + paths, dir, success = complete_path("", use_envpath=false, shell_escape=true) + return paths, r, success + elseif all(arg -> arg isa AbstractString, ex.args) + # Join these and treat this as a path + path::String = join(ex.args) + r = last_arg_start:pos # Also try looking into the env path if the user wants to complete the first argument - use_envpath = !ignore_last_word && length(args.args) < 2 + use_envpath = length(args.args) < 2 - return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true) - elseif isexpr(arg, :incomplete) || isexpr(arg, :error) - partial = scs[last_parse] - ret, range = completions(partial, lastindex(partial)) - range = range .+ (first(last_parse) - 1) - return ret, range, true + # TODO: call complete_expanduser here? + + paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true) + + if success && !isempty(dir) + let dir = do_shell_escape(dir) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + partial = string[last_arg_start:pos] + if endswith(dir, "/") && startswith(partial, dir) + r = (last_arg_start + sizeof(dir)):pos + elseif startswith(partial, dir * "/") + r = nextind(string, last_arg_start + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end + + return paths, r, success end return Completion[], 0:-1, false end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index c5a2d1c8e006e..63b629c5aa784 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -157,7 +157,7 @@ end test_complete(s) = map_completion_text(@inferred(completions(s, lastindex(s)))) test_scomplete(s) = map_completion_text(@inferred(shell_completions(s, lastindex(s)))) test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s, lastindex(s)))[2]) -test_complete_context(s, m) = map_completion_text(@inferred(completions(s,lastindex(s), m))) +test_complete_context(s, m=@__MODULE__) = map_completion_text(@inferred(completions(s,lastindex(s), m))) test_complete_foo(s) = test_complete_context(s, Main.CompletionFoo) test_complete_noshift(s) = map_completion_text(@inferred(completions(s, lastindex(s), Main, false))) @@ -325,7 +325,7 @@ end let s = "\\alpha" c, r = test_bslashcomplete(s) @test c[1] == "α" - @test r == 1:length(s) + @test r == 1:lastindex(s) @test length(c) == 1 end @@ -1041,7 +1041,7 @@ let s, c, r s = "@show \"/dev/nul\"" c,r = completions(s, 15) c = map(completion_text, c) - @test "null" in c + @test "null\"" in c @test r == 13:15 @test s[r] == "nul" @@ -1065,8 +1065,8 @@ let s, c, r if !isdir(joinpath(s, "tmp")) c,r = test_scomplete(s) @test !("tmp/" in c) - @test r === length(s) + 1:0 - @test s[r] == "" + @test !("$s/tmp/" in c) + @test r === (sizeof(s) + 1):sizeof(s) end s = "cd \$(Iter" @@ -1091,7 +1091,7 @@ let s, c, r touch(file) s = string(tempdir(), "/repl\\ ") c,r = test_scomplete(s) - @test ["repl\\ completions"] == c + @test ["'repl completions'"] == c @test s[r] == "repl\\ " rm(file) end @@ -1185,7 +1185,7 @@ let current_dir, forbidden catch e e isa Base.IOError && occursin("ELOOP", e.msg) end - c, r = test_complete("\""*escape_string(joinpath(path, "selfsym"))) + c, r = test_complete("\"$(escape_string(path))/selfsym") @test c == ["selfsymlink"] end end @@ -1222,20 +1222,20 @@ mktempdir() do path dir_space = replace(space_folder, " " => "\\ ") s = Sys.iswindows() ? "cd $dir_space\\\\space" : "cd $dir_space/space" c, r = test_scomplete(s) - @test s[r] == "space" - @test "space\\ .file" in c + @test s[r] == (Sys.iswindows() ? "$dir_space\\\\space" : "$dir_space/space") + @test "'$space_folder'/'space .file'" in c # Also use shell escape rules within cmd backticks s = "`$s" c, r = test_scomplete(s) - @test s[r] == "space" - @test "space\\ .file" in c + @test s[r] == (Sys.iswindows() ? "$dir_space\\\\space" : "$dir_space/space") + @test "'$space_folder'/'space .file'" in c # escape string according to Julia escaping rules - julia_esc(str) = escape_string(str, ('\"','$')) + julia_esc(str) = REPL.REPLCompletions.do_string_escape(str) # For normal strings the string should be properly escaped according to # the usual rules for Julia strings. - s = "cd(\"" * julia_esc(joinpath(path, space_folder, "space")) + s = "cd(\"" * julia_esc(joinpath(path, space_folder) * "/space") c, r = test_complete(s) @test s[r] == "space" @test "space .file\"" in c @@ -1244,7 +1244,7 @@ mktempdir() do path # which needs to be escaped in Julia strings (on unix we could do this # test with all sorts of special chars) touch(joinpath(space_folder, "needs_escape\$.file")) - escpath = julia_esc(joinpath(path, space_folder, "needs_escape\$")) + escpath = julia_esc(joinpath(path, space_folder) * "/needs_escape\$") s = "cd(\"$escpath" c, r = test_complete(s) @test s[r] == "needs_escape\\\$" @@ -1281,12 +1281,12 @@ mktempdir() do path # in shell commands the shell path completion cannot complete # paths with these characters c, r, res = test_scomplete(test_dir) - @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") + @test c[1] == "'$test_dir/'" @test res end escdir = julia_esc(test_dir) c, r, res = test_complete("\""*escdir) - @test c[1] == escdir*(Sys.iswindows() ? "\\\\" : "/") + @test c[1] == escdir * "/" @test res finally rm(joinpath(path, test_dir), recursive=true) @@ -1322,27 +1322,43 @@ if Sys.iswindows() cd(path) do s = "cd ..\\\\" c,r = test_scomplete(s) - @test r == length(s)+1:length(s) - @test temp_name * "\\\\" in c + @test r == lastindex(s)-3:lastindex(s) + @test "../$temp_name/" in c + + s = "cd ../" + c,r = test_scomplete(s) + @test r == lastindex(s)+1:lastindex(s) + @test "$temp_name/" in c s = "ls $(file[1:2])" c,r = test_scomplete(s) - @test r == length(s)-1:length(s) + @test r == lastindex(s)-1:lastindex(s) @test file in c s = "cd(\"..\\\\" c,r = test_complete(s) - @test r == length(s)+1:length(s) - @test temp_name * "\\\\" in c + @test r == lastindex(s)-3:lastindex(s) + @test "../$temp_name/" in c + + s = "cd(\"../" + c,r = test_complete(s) + @test r == lastindex(s)+1:lastindex(s) + @test "$temp_name/" in c s = "cd(\"$(file[1:2])" c,r = test_complete(s) - @test r == length(s) - 1:length(s) + @test r == lastindex(s) - 1:lastindex(s) @test (length(c) > 1 && file in c) || (["$file\""] == c) end rm(tmp) end +# issue 51985 +let s = "`\\" + c,r = test_scomplete(s) + @test r == lastindex(s)+1:lastindex(s) +end + # auto completions of true and false... issue #14101 let s = "tru" c, r, res = test_complete(s) @@ -1825,7 +1841,7 @@ function Base.getproperty(v::Issue36437, s::Symbol) end let s = "Issue36437(42)." - c, r, res = test_complete_context(s, @__MODULE__) + c, r, res = test_complete_context(s) @test res for n in ("a", "b", "c") @test n in c @@ -1833,7 +1849,7 @@ let s = "Issue36437(42)." end let s = "Some(Issue36437(42)).value." - c, r, res = test_complete_context(s, @__MODULE__) + c, r, res = test_complete_context(s) @test res for n in ("a", "b", "c") @test n in c @@ -1842,7 +1858,7 @@ end # aggressive concrete evaluation on mutable allocation in `repl_frame` let s = "Ref(Issue36437(42))[]." - c, r, res = test_complete_context(s, @__MODULE__) + c, r, res = test_complete_context(s) @test res for n in ("a", "b", "c") @test n in c @@ -1852,7 +1868,7 @@ end const global_xs = [Some(42)] let s = "pop!(global_xs)." - c, r, res = test_complete_context(s, @__MODULE__) + c, r, res = test_complete_context(s) @test res @test "value" in c end @@ -1884,7 +1900,7 @@ end Issue49892(x) = x let s = "Issue49892(fal" - c, r, res = test_complete_context(s, @__MODULE__) + c, r, res = test_complete_context(s) @test res for n in ("false", "falses") @test n in c @@ -1904,3 +1920,15 @@ for (s, compl) in (("2*CompletionFoo.nam", "named"), c, r = test_complete(s) @test only(c) == compl end + +# allows symbol completion within incomplete :macrocall +# https://github.com/JuliaLang/julia/issues/51827 +macro issue51827(args...) + length(args) ≥ 2 || error("@issue51827: incomplete arguments") + return args +end +let s = "@issue51827 Base.ac" + c, r, res = test_complete_context(s) + @test res + @test "acquire" in c +end diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index b30b1002d5285..8ff55582ca9df 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = fa6269b0dc5660cbaddb9365c7d89b206fc55992 +SPARSEARRAYS_SHA1 = 279b363ca8d3129d4742903d37c8b11545fa08a2 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/Statistics.version b/stdlib/Statistics.version index 05d7839223924..336d7c731bd47 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = 08562cb8abbe67829e437aa8533994121c350c05 +STATISTICS_SHA1 = d147f9253aa0f2f71be9c3fed8d51c2215410408 STATISTICS_GIT_URL := https://github.com/JuliaStats/Statistics.jl.git STATISTICS_TAR_URL = https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/$1 diff --git a/stdlib/Unicode/test/runtests.jl b/stdlib/Unicode/test/runtests.jl index 5c5a75b33e363..573d5da6167ba 100644 --- a/stdlib/Unicode/test/runtests.jl +++ b/stdlib/Unicode/test/runtests.jl @@ -27,8 +27,8 @@ using Unicode: normalize, isassigned, julia_chartransform @test normalize("\u0072\u0307\u0323", :NFC) == "\u1E5B\u0307" #26917 # julia_chartransform identifier normalization - @test normalize("julia\u025B\u00B5\u00B7\u0387\u2212", chartransform=julia_chartransform) == - "julia\u03B5\u03BC\u22C5\u22C5\u002D" + @test normalize("julia\u025B\u00B5\u00B7\u0387\u2212\u210F", chartransform=julia_chartransform) == + "julia\u03B5\u03BC\u22C5\u22C5\u002D\u0127" @test julia_chartransform('\u00B5') === '\u03BC' end diff --git a/test/channels.jl b/test/channels.jl index 36fec7b842de1..82689e8d08d28 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -457,8 +457,8 @@ end Sys.iswindows() && Base.process_events() # schedule event (windows?) close(async) # and close @test !isopen(async) - @test tc[] == 2 - @test tc[] == 2 + @test tc[] == 3 + @test tc[] == 3 yield() # consume event & then close @test tc[] == 3 sleep(0.1) # no further events @@ -479,7 +479,7 @@ end close(async) @test !isopen(async) Base.process_events() # and close - @test tc[] == 0 + @test tc[] == 1 yield() # consume event & then close @test tc[] == 1 sleep(0.1) # no further events diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 417ac82973558..1730fe8179857 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -66,7 +66,9 @@ end get_julia_cmd(arg) = strip(read(`$julia_basic $arg -e 'print(repr(Base.julia_cmd()))'`, String), ['`']) for (arg, default) in ( - ("-C$(unsafe_string(opts.cpu_target))", false), + # Use a Cmd to handle space nicely when + # interpolating inside another Cmd. + (`-C $(unsafe_string(opts.cpu_target))`, false), ("-J$(unsafe_string(opts.image_file))", false), @@ -123,13 +125,21 @@ end ("--pkgimages=no", false), ) @testset "$arg" begin + str = arg isa Cmd ? join(arg.exec, ' ') : arg if default - @test !occursin(arg, get_julia_cmd(arg)) + @test !occursin(str, get_julia_cmd(arg)) else - @test occursin(arg, get_julia_cmd(arg)) + @test occursin(str, get_julia_cmd(arg)) end end end + + # Test empty `cpu_target` gives a helpful error message, issue #52209. + io = IOBuffer() + p = run(pipeline(`$(Base.julia_cmd(; cpu_target="")) --startup-file=no -e ''`; stderr=io); wait=false) + wait(p) + @test p.exitcode == 1 + @test occursin("empty CPU name", String(take!(io))) end let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @@ -465,10 +475,47 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test occursin(expected, got) || (expected, got) @test_broken occursin(expected_good, got) + # Ask for coverage in current directory + tdir = dirname(realpath(inputfile)) + cd(tdir) do + # there may be atrailing separator here so use rstrip + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, rstrip(unsafe_string(Base.JLOptions().tracked_path), '/'))" -L $inputfile + --code-coverage=$covfile --code-coverage=@`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) + @test_broken occursin(expected_good, got) + + # Ask for coverage in relative directory + tdir = dirname(realpath(inputfile)) + cd(dirname(tdir)) do + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile + --code-coverage=$covfile --code-coverage=@testhelpers`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) + @test_broken occursin(expected_good, got) + + # Ask for coverage in relative directory with dot-dot notation + tdir = dirname(realpath(inputfile)) + cd(tdir) do + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile + --code-coverage=$covfile --code-coverage=@../testhelpers`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) + @test_broken occursin(expected_good, got) + # Ask for coverage in a different directory tdir = mktempdir() # a dir that contains no code @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile - --code-coverage=$covfile --code-coverage=@$tdir`) == "(3, $(repr(tdir)))" + --code-coverage=$covfile --code-coverage=@$tdir`) == "(3, $(repr(realpath(tdir))))" @test isfile(covfile) got = read(covfile, String) @test isempty(got) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 9b34ae7321ff6..88f9948bb30a5 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -848,3 +848,7 @@ let res = @timed a50317[:b] @test res.bytes == 0 return res end + +# https://github.com/JuliaLang/julia/issues/51233 +obj51233 = (1,) +@test_throws ErrorException obj51233.x diff --git a/test/llvmcall2.jl b/test/llvmcall2.jl index 07b27fc407433..e3e89bb916f2d 100644 --- a/test/llvmcall2.jl +++ b/test/llvmcall2.jl @@ -73,3 +73,12 @@ end jl_str = unsafe_string(str) @test length(jl_str) > 4 end + + +# boolean structs +const NT4I = NTuple{4, VecElement{Int}} +const NT4B = NTuple{4, VecElement{Bool}} +f_nt4b(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4B, NT4B}, (NT4B, NT4B), x, y) +f_nt4i(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4I, NT4B}, (NT4I, NT4I), x, y) +@test f_nt4b((false, true, false, true), (false, false, true, true)) === (NT4B((false, true, true, false)) => NT4B((false, false, false, true))) +@test f_nt4i((typemin(Int), 0, typemax(Int), typemax(Int)), (-1, typemax(Int),-1, 1)) === (NT4I((typemax(Int), typemax(Int), typemax(Int)-1, typemin(Int))) => NT4B((true, false, false, true))) diff --git a/test/llvmpasses/alloc-opt-gcframe-addrspaces.ll b/test/llvmpasses/alloc-opt-gcframe-addrspaces.ll index b33f2cdac7dd4..1eefa7e56922f 100644 --- a/test/llvmpasses/alloc-opt-gcframe-addrspaces.ll +++ b/test/llvmpasses/alloc-opt-gcframe-addrspaces.ll @@ -19,15 +19,16 @@ declare {}* @julia.pointer_from_objref({} addrspace(11)*) ; Test that non-0 addrspace allocas are properly emitted and handled ; CHECK-LABEL: @non_zero_addrspace -; CHECK: %1 = alloca i32, align 8, addrspace(5) +; TYPED: %1 = alloca i32, align 8, addrspace(5) ; TYPED: %2 = bitcast i32 addrspace(5)* %1 to i8 addrspace(5)* -; TYPED: %3 = bitcast i8 addrspace(5)* %2 to {} addrspace(5)* -; TYPED: %var1 = addrspacecast {} addrspace(5)* %3 to {} addrspace(10)* +; TYPED: %var1 = bitcast i8 addrspace(5)* %2 to {} addrspace(5)* +; TYPED: %3 = addrspacecast {} addrspace(5)* %var1 to {}* ; TYPED: call void @llvm.lifetime.start.p5i8(i64 4, i8 addrspace(5)* %2) -; OPAQUE: %var1 = addrspacecast ptr addrspace(5) %1 to ptr addrspace(10) -; OPAQUE: call void @llvm.lifetime.start.p5(i64 4, ptr addrspace(5) %1) +; OPAQUE: %var1 = alloca i32, align 8, addrspace(5) +; OPAQUE: %1 = addrspacecast ptr addrspace(5) %var1 to ptr +; OPAQUE: call void @llvm.lifetime.start.p5(i64 4, ptr addrspace(5) %var1) ; CHECK: ret void define void @non_zero_addrspace() { diff --git a/test/llvmpasses/cpu-features.ll b/test/llvmpasses/cpu-features.ll index eea3d1b288204..080b6e07d3086 100644 --- a/test/llvmpasses/cpu-features.ll +++ b/test/llvmpasses/cpu-features.ll @@ -5,6 +5,8 @@ ; RUN: opt -enable-new-pm=0 --opaque-pointers=1 -load libjulia-codegen%shlibext -CPUFeatures -simplifycfg -S %s | FileCheck %s ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" +target triple = "x86_64-linux-gnu" declare i1 @julia.cpu.have_fma.f64() declare double @with_fma(double %0, double %1, double %2) diff --git a/test/llvmpasses/multiversioning-x86.ll b/test/llvmpasses/multiversioning-x86.ll new file mode 100644 index 0000000000000..ca43462e1eda9 --- /dev/null +++ b/test/llvmpasses/multiversioning-x86.ll @@ -0,0 +1,132 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=1 --opaque-pointers=0 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning,CPUFeatures' -S %s | FileCheck %s + +; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning,CPUFeatures' -S %s | FileCheck %s + +; COM: This test checks that multiversioning actually happens from start to finish +; COM: We need the fvars for a proper test + + + +; CHECK: @jl_fvar_idxs = hidden constant [5 x i32] [i32 0, i32 1, i32 2, i32 3, i32 4], align 16 +; CHECK: @jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 16 +; TYPED: @simd_test.reloc_slot = hidden global i32 (<4 x i32>)* null +; OPAQUE: @simd_test.reloc_slot = hidden global ptr null +; TYPED: @jl_fvar_offsets = hidden constant [6 x i32] [i32 5, i32 0, i32 trunc (i64 sub (i64 ptrtoint (float (float, float)* @fastmath_test to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (i32)* @loop_test to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test_call to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32)] +; OPAQUE: @jl_fvar_offsets = hidden constant [6 x i32] [i32 5, i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @fastmath_test to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @loop_test to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test_call to i64), i64 ptrtoint (ptr @boring to i64)) to i32)] +; CHECK: @jl_gvar_base = hidden constant i64 0 +; CHECK: @jl_gvar_offsets = hidden constant [1 x i32] zeroinitializer +; TYPED: @jl_clone_slots = hidden constant [3 x i32] [i32 1, i32 3, i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)** @simd_test.reloc_slot to i64), i64 ptrtoint (i64* @jl_gvar_base to i64)) to i32)] +; OPAQUE: @jl_clone_slots = hidden constant [3 x i32] [i32 1, i32 3, i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test.reloc_slot to i64), i64 ptrtoint (ptr @jl_gvar_base to i64)) to i32)] +; CHECK: @jl_clone_idxs = hidden constant [10 x i32] [i32 -2147483647, i32 3, i32 -2147483647, i32 3, i32 4, i32 1, i32 1, i32 2, i32 -2147483645, i32 4] +; TYPED: @jl_clone_offsets = hidden constant [9 x i32] [i32 trunc (i64 sub (i64 ptrtoint (i32 (i32)* @boring.1 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (float (float, float)* @fastmath_test.1 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (i32)* @loop_test.1 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test.1 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test_call.1 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (float (float, float)* @fastmath_test.2 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (i32)* @loop_test.2 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test.2 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)* @simd_test_call.2 to i64), i64 ptrtoint (i32 (i32)* @boring to i64)) to i32)] +; OPAQUE: @jl_clone_offsets = hidden constant [9 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @boring.1 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @fastmath_test.1 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @loop_test.1 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test.1 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test_call.1 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @fastmath_test.2 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @loop_test.2 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test.2 to i64), i64 ptrtoint (ptr @boring to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test_call.2 to i64), i64 ptrtoint (ptr @boring to i64)) to i32)] +; TYPED: @jl_fvar_base = hidden alias i64, bitcast (i32 (i32)* @boring to i64*) +; OPAQUE: @jl_fvar_base = hidden alias i64, ptr @boring + + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" +target triple = "x86_64-linux-gnu" + +@jl_fvars = global [5 x i64*] [i64* bitcast (i32 (i32)* @boring to i64*), + i64* bitcast (float (float, float)* @fastmath_test to i64*), + i64* bitcast (i32 (i32)* @loop_test to i64*), + i64* bitcast (i32 (<4 x i32>)* @simd_test to i64*), + i64* bitcast (i32 (<4 x i32>)* @simd_test_call to i64*) + ], align 16 +@jl_gvars = global [0 x i64*] zeroinitializer, align 16 +@jl_fvar_idxs = hidden constant [5 x i32] [i32 0, i32 1, i32 2, i32 3, i32 4], align 16 +@jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 16 + +declare i1 @julia.cpu.have_fma.f32() + +; CHECK: @boring{{.*}}#[[BORING_BASE:[0-9]+]] +define noundef i32 @boring(i32 noundef %0) { + ret i32 %0 +} + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +; CHECK: %3 = sitofp i1 false to float +define noundef float @fastmath_test(float noundef %0, float noundef %1) { + %3 = call i1 @julia.cpu.have_fma.f32() + %4 = sitofp i1 %3 to float + %5 = fadd fast float %0, %4 + ret float %5 +} + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +define noundef i32 @loop_test(i32 noundef %0) { + %2 = icmp sgt i32 %0, 0 + br i1 %2, label %5, label %3 + +3: ; preds = %5, %1 + %4 = phi i32 [ 0, %1 ], [ %9, %5 ] + ret i32 %4 + +5: ; preds = %1, %5 + %6 = phi i32 [ %10, %5 ], [ 0, %1 ] + %7 = phi i32 [ %9, %5 ], [ 0, %1 ] + %8 = lshr i32 %6, 1 + %9 = add nuw nsw i32 %8, %7 + %10 = add nuw nsw i32 %6, 1 + %11 = icmp eq i32 %10, %0 + br i1 %11, label %3, label %5;, !llvm.loop - +} + +; CHECK: @simd_test{{.*}}#[[SIMD_BASE_RELOC:[0-9]+]] +define noundef i32 @simd_test(<4 x i32> noundef %0) { + %2 = extractelement <4 x i32> %0, i64 0 + ret i32 %2 +} + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +define noundef i32 @simd_test_call(<4 x i32> noundef %0) { + %2 = call noundef i32 @simd_test(<4 x i32> noundef %0) + ret i32 %2 +} + +; CHECK: @boring{{.*}}#[[BORING_CLONE:[0-9]+]] + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] +; CHECK: %3 = sitofp i1 false to float + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] +; CHECK: %3 = sitofp i1 true to float + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] + +; CHECK: @simd_test{{.*}}#[[SIMD_CLONE1:[0-9]+]] + +; CHECK: @simd_test{{.*}}#[[SIMD_CLONE2:[0-9]+]] + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] +; TYPED: %2 = load i32 (<4 x i32>)*, i32 (<4 x i32>)** @simd_test.reloc_slot, align 8, !tbaa !8, !invariant.load !12 +; OPAQUE: %2 = load ptr, ptr @simd_test.reloc_slot, align 8, !tbaa !8, !invariant.load !12 +; CHECK: %3 = call noundef i32 %2(<4 x i32> noundef %0) + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] +; CHECK: %2 = call noundef i32 @simd_test.2(<4 x i32> noundef %0) + +; CHECK-DAG: attributes #[[BORING_BASE]] = { "julia.mv.clone"="0" "julia.mv.clones"="2" "julia.mv.fvar" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_BASE]] = { "julia.mv.clone"="0" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_BASE_RELOC]] = { "julia.mv.clone"="0" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[BORING_CLONE]] = { "julia.mv.clone"="1" "julia.mv.clones"="2" "julia.mv.fvar" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_CLONE1]] = { "julia.mv.clone"="1" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_CLONE2]] = { "julia.mv.clone"="2" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="haswell" "target-features"="+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_CLONE1]] = { "julia.mv.clone"="1" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_CLONE2]] = { "julia.mv.clone"="2" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="haswell" "target-features"="+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } + + +!llvm.module.flags = !{!0, !2} + + +!0 = !{i32 1, !"julia.mv.enable", i32 1} +!1 = !{!1} +!2 = !{i32 1, !"julia.mv.specs", !3} +!3 = !{!4, !5, !6} +!4 = !{!"x86-64", !"+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 0, i32 0} +!5 = !{!"sandybridge", !"+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 0, i32 2} +!6 = !{!"haswell", !"+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 1, i32 284} diff --git a/test/precompile.jl b/test/precompile.jl index 82f445a115fee..e10d896da7d3f 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -504,6 +504,17 @@ precompile_test_harness(false) do dir """) @test Base.compilecache(Base.PkgId("Baz")) == Base.PrecompilableError() # due to __precompile__(false) + + OverwriteMethodError_file = joinpath(dir, "OverwriteMethodError.jl") + write(OverwriteMethodError_file, + """ + module OverwriteMethodError + Base.:(+)(x::Bool, y::Bool) = false + end + """) + + @test Base.compilecache(Base.PkgId("OverwriteMethodError")) == Base.PrecompilableError() # due to piracy + @eval using Baz @test Base.invokelatest(Baz.baz) == 1 diff --git a/test/sorting.jl b/test/sorting.jl index 147a70a5db7d9..81376ab5cd114 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -88,20 +88,6 @@ end vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99) end -function tuple_sort_test(x) - @test issorted(sort(x)) - length(x) > 9 && return # length > 9 uses a vector fallback - @test 0 == @allocated sort(x) -end -@testset "sort(::NTuple)" begin - @test sort((9,8,3,3,6,2,0,8)) == (0,2,3,3,6,8,8,9) - @test sort((9,8,3,3,6,2,0,8), by=x->x÷3) == (2,0,3,3,8,6,8,9) - for i in 1:40 - tuple_sort_test(tuple(rand(i)...)) - end - @test_throws ArgumentError sort((1,2,3.0)) -end - @testset "partialsort" begin @test partialsort([3,6,30,1,9],3) == 6 @test partialsort([3,6,30,1,9],3:4) == [6,9] @@ -544,23 +530,6 @@ end @test isequal(a, [8,6,7,NaN,5,3,0,9]) end -@testset "sort!(iterable)" begin - gen = (x % 7 + 0.1x for x in 1:50) - @test sort(gen) == sort!(collect(gen)) - gen = (x % 7 + 0.1y for x in 1:10, y in 1:5) - @test sort(gen; dims=1) == sort!(collect(gen); dims=1) - @test sort(gen; dims=2) == sort!(collect(gen); dims=2) - - @test_throws ArgumentError("dimension out of range") sort(gen; dims=3) - - @test_throws UndefKeywordError(:dims) sort(gen) - @test_throws UndefKeywordError(:dims) sort(collect(gen)) - @test_throws UndefKeywordError(:dims) sort!(collect(gen)) - - @test_throws ArgumentError sort("string") - @test_throws ArgumentError("1 cannot be sorted") sort(1) -end - @testset "sort!(::AbstractVector{<:Integer}) with short int range" begin a = view([9:-1:0;], :)::SubArray sort!(a) diff --git a/test/spawn.jl b/test/spawn.jl index 3fdfa794ff39e..1fab652199ee0 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -660,9 +660,21 @@ let p = run(`$sleepcmd 100`, wait=false) kill(p) end -# Second argument of shell_parse +# Second return of shell_parse let s = " \$abc " - @test s[Base.shell_parse(s)[2]] == "abc" + @test Base.shell_parse(s)[2] === findfirst('a', s) + s = "abc def" + @test Base.shell_parse(s)[2] === findfirst('d', s) + s = "abc 'de'f\"\"g" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc \$x'de'f\"\"g" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc def\$x'g'" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc def\$x " + @test Base.shell_parse(s)[2] === findfirst('x', s) + s = "abc \$(d)ef\$(x " + @test Base.shell_parse(s)[2] === findfirst('x', s) - 1 end # Logging macros should not output to finalized streams (#26687) diff --git a/test/syntax.jl b/test/syntax.jl index c69843785136e..3516c306c42d4 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1783,6 +1783,43 @@ end @test B28593.var.name === :S @test C28593.var.name === :S +# issue #51899 +macro struct_macro_51899() + quote + mutable struct Struct51899 + const const_field + const const_field_with_type::Int + $(esc(Expr(:const, :(escaped_const_field::MyType)))) + @atomic atomic_field + @atomic atomic_field_with_type::Int + end + end +end + +let ex = @macroexpand @struct_macro_51899() + const_field, const_field_with_type, escaped_const_field, + atomic_field, atomic_field_with_type = filter(x -> isa(x, Expr), ex.args[end].args[end].args) + @test Meta.isexpr(const_field, :const) + @test const_field.args[1] === :const_field + + @test Meta.isexpr(const_field_with_type, :const) + @test Meta.isexpr(const_field_with_type.args[1], :(::)) + @test const_field_with_type.args[1].args[1] === :const_field_with_type + @test const_field_with_type.args[1].args[2] == GlobalRef(@__MODULE__, :Int) + + @test Meta.isexpr(escaped_const_field, :const) + @test Meta.isexpr(const_field_with_type.args[1], :(::)) + @test escaped_const_field.args[1].args[1] === :escaped_const_field + @test escaped_const_field.args[1].args[2] === :MyType + + @test Meta.isexpr(atomic_field, :atomic) + @test atomic_field.args[1] === :atomic_field + + @test Meta.isexpr(atomic_field_with_type, :atomic) + @test atomic_field_with_type.args[1].args[1] === :atomic_field_with_type + @test atomic_field_with_type.args[1].args[2] == GlobalRef(@__MODULE__, :Int) +end + # issue #25955 macro noeffect25955(e) return e diff --git a/test/sysinfo.jl b/test/sysinfo.jl index cb943cfd38843..f2e233d608338 100644 --- a/test/sysinfo.jl +++ b/test/sysinfo.jl @@ -12,6 +12,8 @@ Base.Sys.loadavg() @test length(ccall(:jl_get_cpu_name, String, ())) != 0 @test length(ccall(:jl_get_cpu_features, String, ())) >= 0 +foo_fma() = Core.Intrinsics.have_fma(Int64) +@test ccall(:jl_cpu_has_fma, Bool, (Cint,), 64) == foo_fma() if Sys.isunix() mktempdir() do tempdir