diff --git a/base/Base.jl b/base/Base.jl index f39b227d6f663..9ad0cdf7a6661 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -276,9 +276,6 @@ include("weakkeydict.jl") include("env.jl") -# BinaryPlatforms, used by Artifacts -include("binaryplatforms.jl") - # functions defined in Random function rand end function randn end @@ -336,6 +333,9 @@ using .Order include("sort.jl") using .Sort +# BinaryPlatforms, used by Artifacts. Needs `Sort`. +include("binaryplatforms.jl") + # Fast math include("fastmath.jl") using .FastMath diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 50b83dff86e6b..9c3cb23865dff 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -1712,13 +1712,15 @@ end _cs(d, a, b) = (a == b ? a : throw(DimensionMismatch( "mismatch in dimension $d (expected $a got $b)"))) -function dims2cat(::Val{n}) where {n} - n <= 0 && throw(ArgumentError("cat dimension must be a positive integer, but got $n")) - ntuple(i -> (i == n), Val(n)) +function dims2cat(::Val{dims}) where dims + if any(≤(0), dims) + throw(ArgumentError("All cat dimensions must be positive integers, but got $dims")) + end + ntuple(in(dims), maximum(dims)) end function dims2cat(dims) - if any(dims .<= 0) + if any(≤(0), dims) throw(ArgumentError("All cat dimensions must be positive integers, but got $dims")) end ntuple(in(dims), maximum(dims)) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index a9c04dac22b7b..daab71c3cc671 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -189,7 +189,10 @@ empty(a::AbstractDict) = empty(a, keytype(a), valtype(a)) empty(a::AbstractDict, ::Type{V}) where {V} = empty(a, keytype(a), V) # Note: this is the form which makes sense for `Vector`. copy(a::AbstractDict) = merge!(empty(a), a) -copy!(dst::AbstractDict, src::AbstractDict) = merge!(empty!(dst), src) +function copy!(dst::AbstractDict, src::AbstractDict) + dst === src && return dst + merge!(empty!(dst), src) +end """ merge!(d::AbstractDict, others::AbstractDict...) diff --git a/base/abstractset.jl b/base/abstractset.jl index bec4a84b19d15..561e18c15697c 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -3,7 +3,10 @@ eltype(::Type{<:AbstractSet{T}}) where {T} = @isdefined(T) ? T : Any sizehint!(s::AbstractSet, n) = nothing -copy!(dst::AbstractSet, src::AbstractSet) = union!(empty!(dst), src) +function copy!(dst::AbstractSet, src::AbstractSet) + dst === src && return dst + union!(empty!(dst), src) +end ## set operations (union, intersection, symmetric difference) diff --git a/base/array.jl b/base/array.jl index cf5bbc05e412a..b8ad7e137f25e 100644 --- a/base/array.jl +++ b/base/array.jl @@ -402,17 +402,20 @@ julia> getindex(Int8, 1, 2, 3) """ function getindex(::Type{T}, vals...) where T a = Vector{T}(undef, length(vals)) - @inbounds for i = 1:length(vals) - a[i] = vals[i] + if vals isa NTuple + @inbounds for i in 1:length(vals) + a[i] = vals[i] + end + else + # use afoldl to avoid type instability inside loop + afoldl(1, vals...) do i, v + @inbounds a[i] = v + return i + 1 + end end return a end -getindex(::Type{T}) where {T} = (@inline; Vector{T}()) -getindex(::Type{T}, x) where {T} = (@inline; a = Vector{T}(undef, 1); @inbounds a[1] = x; a) -getindex(::Type{T}, x, y) where {T} = (@inline; a = Vector{T}(undef, 2); @inbounds (a[1] = x; a[2] = y); a) -getindex(::Type{T}, x, y, z) where {T} = (@inline; a = Vector{T}(undef, 3); @inbounds (a[1] = x; a[2] = y; a[3] = z); a) - function getindex(::Type{Any}, @nospecialize vals...) a = Vector{Any}(undef, length(vals)) @inbounds for i = 1:length(vals) diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index efc58dc6c6b7a..61e1af796999d 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -608,7 +608,8 @@ const arch_march_isa_mapping = let "armv8_0" => get_set("aarch64", "armv8.0-a"), "armv8_1" => get_set("aarch64", "armv8.1-a"), "armv8_2_crypto" => get_set("aarch64", "armv8.2-a+crypto"), - "armv8_4_crypto_sve" => get_set("aarch64", "armv8.4-a+crypto+sve"), + "a64fx" => get_set("aarch64", "a64fx"), + "apple_m1" => get_set("aarch64", "apple_m1"), ], "powerpc64le" => [ "power8" => get_set("powerpc64le", "power8"), diff --git a/base/cmd.jl b/base/cmd.jl index 5094dea908440..ab639a2b185c8 100644 --- a/base/cmd.jl +++ b/base/cmd.jl @@ -262,6 +262,15 @@ setenv(cmd::Cmd, env::Pair{<:AbstractString}...; dir=cmd.dir) = setenv(cmd, env; dir=dir) setenv(cmd::Cmd; dir=cmd.dir) = Cmd(cmd; dir=dir) +# split environment entry string into before and after first `=` (key and value) +function splitenv(e::String) + i = findnext('=', e, 2) + if i === nothing + throw(ArgumentError("malformed environment entry")) + end + e[1:prevind(e, i)], e[nextind(e, i):end] +end + """ addenv(command::Cmd, env...; inherit::Bool = true) @@ -282,7 +291,7 @@ function addenv(cmd::Cmd, env::Dict; inherit::Bool = true) merge!(new_env, ENV) end else - for (k, v) in eachsplit.(cmd.env, "=") + for (k, v) in splitenv.(cmd.env) new_env[string(k)::String] = string(v)::String end end @@ -301,7 +310,7 @@ function addenv(cmd::Cmd, pairs::Pair{<:AbstractString}...; inherit::Bool = true end function addenv(cmd::Cmd, env::Vector{<:AbstractString}; inherit::Bool = true) - return addenv(cmd, Dict(k => v for (k, v) in eachsplit.(env, "=")); inherit) + return addenv(cmd, Dict(k => v for (k, v) in splitenv.(env)); inherit) end """ diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6a9837547834b..39cabe33ff043 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -65,13 +65,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), const_results = Union{InferenceResult,Nothing,ConstResult}[] multiple_matches = napplicable > 1 - if f !== nothing && napplicable == 1 && is_method_pure(applicable[1]::MethodMatch) - val = pure_eval_call(f, argtypes) - if val !== nothing - # TODO: add some sort of edge(s) - return CallMeta(val, MethodResultPure(info)) - end - end + val = pure_eval_call(interp, f, applicable, arginfo, sv) + val !== nothing && return CallMeta(val, MethodResultPure(info)) # TODO: add some sort of edge(s) fargs = arginfo.fargs for i in 1:napplicable @@ -99,10 +94,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) - const_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) + const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) effects = result.edge_effects - if const_result !== nothing - (;rt, effects, const_result) = const_result + const_result = nothing + if const_call_result !== nothing + if const_call_result.rt ⊑ rt + (; rt, effects, const_result) = const_call_result + end end tristate_merge!(sv, effects) push!(const_results, const_result) @@ -138,11 +136,17 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # this is in preparation for inlining, or improving the return result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) - const_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) + const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) effects = result.edge_effects - if const_result !== nothing - this_rt = const_result.rt - (; effects, const_result) = const_result + const_result = nothing + if const_call_result !== nothing + this_const_rt = const_call_result.rt + # return type of const-prop' inference can be wider than that of non const-prop' inference + # e.g. in cases when there are cycles but cached result is still accurate + if this_const_rt ⊑ this_rt + this_rt = this_const_rt + (; effects, const_result) = const_call_result + end end tristate_merge!(sv, effects) push!(const_results, const_result) @@ -594,15 +598,27 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp if edge === nothing edgecycle = edgelimited = true end - if edgecycle + if is_effect_overrided(sv, :terminates_globally) + # this frame is known to terminate + edge_effects = Effects(edge_effects, terminates=ALWAYS_TRUE) + elseif is_effect_overrided(method, :terminates_globally) + # this edge is known to terminate + edge_effects = Effects(edge_effects, terminates=ALWAYS_TRUE) + elseif edgecycle # Some sort of recursion was detected. Even if we did not limit types, - # we cannot guarantee that the call will terminate. - edge_effects = tristate_merge(edge_effects, - Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) + # we cannot guarantee that the call will terminate + edge_effects = Effects(edge_effects, terminates=TRISTATE_UNKNOWN) end return MethodCallResult(rt, edgecycle, edgelimited, edge, edge_effects) end +is_effect_overrided(sv::InferenceState, effect::Symbol) = is_effect_overrided(sv.linfo, effect) +function is_effect_overrided(linfo::MethodInstance, effect::Symbol) + def = linfo.def + return isa(def, Method) && is_effect_overrided(def, effect) +end +is_effect_overrided(method::Method, effect::Symbol) = getfield(decode_effects_override(method.purity), effect) + # keeps result and context information of abstract method call, will be used by succeeding constant-propagation struct MethodCallResult rt @@ -619,27 +635,85 @@ struct MethodCallResult end end +function pure_eval_eligible(interp::AbstractInterpreter, + @nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState) + return !isoverlayed(method_table(interp, sv)) && + f !== nothing && + length(applicable) == 1 && + is_method_pure(applicable[1]::MethodMatch) && + is_all_const_arg(arginfo) +end + +function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector) + if isdefined(method, :generator) + method.generator.expand_early || return false + mi = specialize_method(method, sig, sparams) + isa(mi, MethodInstance) || return false + staged = get_staged(mi) + (staged isa CodeInfo && (staged::CodeInfo).pure) || return false + return true + end + return method.pure +end +is_method_pure(match::MethodMatch) = is_method_pure(match.method, match.spec_types, match.sparams) + +function pure_eval_call(interp::AbstractInterpreter, + @nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState) + pure_eval_eligible(interp, f, applicable, arginfo, sv) || return nothing + return _pure_eval_call(f, arginfo) +end +function _pure_eval_call(@nospecialize(f), arginfo::ArgInfo) + args = collect_const_args(arginfo) + try + value = Core._apply_pure(f, args) + return Const(value) + catch + return nothing + end +end + +function concrete_eval_eligible(interp::AbstractInterpreter, + @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) + return !isoverlayed(method_table(interp, sv)) && + f !== nothing && + result.edge !== nothing && + is_total_or_error(result.edge_effects) && + is_all_const_arg(arginfo) +end + function is_all_const_arg((; argtypes)::ArgInfo) - for a in argtypes - if !isa(a, Const) && !isconstType(a) && !issingletontype(a) - return false - end + for i = 2:length(argtypes) + a = widenconditional(argtypes[i]) + isa(a, Const) || isconstType(a) || issingletontype(a) || return false end return true end -function concrete_eval_const_proven_total_or_error(interp::AbstractInterpreter, - @nospecialize(f), (; argtypes)::ArgInfo, _::InferenceState) - args = Any[ (a = widenconditional(argtypes[i]); - isa(a, Const) ? a.val : - isconstType(a) ? (a::DataType).parameters[1] : - (a::DataType).instance) for i in 2:length(argtypes) ] +function collect_const_args((; argtypes)::ArgInfo) + return Any[ let a = widenconditional(argtypes[i]) + isa(a, Const) ? a.val : + isconstType(a) ? (a::DataType).parameters[1] : + (a::DataType).instance + end for i in 2:length(argtypes) ] +end + +function concrete_eval_call(interp::AbstractInterpreter, + @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) + concrete_eval_eligible(interp, f, result, arginfo, sv) || return nothing + args = collect_const_args(arginfo) try value = Core._call_in_world_total(get_world_counter(interp), f, args...) - return Const(value) - catch e - return nothing + if is_inlineable_constant(value) || call_result_unused(sv) + # If the constant is not inlineable, still do the const-prop, since the + # code that led to the creation of the Const may be inlineable in the same + # circumstance and may be optimizable. + return ConstCallResults(Const(value), ConstResult(result.edge, value), EFFECTS_TOTAL) + end + catch + # The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime + return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) end + return nothing end function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch) @@ -671,19 +745,10 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul if !const_prop_enabled(interp, sv, match) return nothing end - if f !== nothing && result.edge !== nothing && is_total_or_error(result.edge_effects) && is_all_const_arg(arginfo) - rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo, sv) + val = concrete_eval_call(interp, f, result, arginfo, sv) + if val !== nothing add_backedge!(result.edge, sv) - if rt === nothing - # The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) - end - if is_inlineable_constant(rt.val) || call_result_unused(sv) - # If the constant is not inlineable, still do the const-prop, since the - # code that led to the creation of the Const may be inlineable in the same - # circumstance and may be optimizable. - return ConstCallResults(rt, ConstResult(result.edge, rt.val), EFFECTS_TOTAL) - end + return val end mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) mi === nothing && return nothing @@ -1218,36 +1283,6 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv:: return CallMeta(res, retinfo) end -function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector) - if isdefined(method, :generator) - method.generator.expand_early || return false - mi = specialize_method(method, sig, sparams) - isa(mi, MethodInstance) || return false - staged = get_staged(mi) - (staged isa CodeInfo && (staged::CodeInfo).pure) || return false - return true - end - return method.pure -end -is_method_pure(match::MethodMatch) = is_method_pure(match.method, match.spec_types, match.sparams) - -function pure_eval_call(@nospecialize(f), argtypes::Vector{Any}) - for i = 2:length(argtypes) - a = widenconditional(argtypes[i]) - if !(isa(a, Const) || isconstType(a)) - return nothing - end - end - args = Any[ (a = widenconditional(argtypes[i]); - isa(a, Const) ? a.val : (a::DataType).parameters[1]) for i in 2:length(argtypes) ] - try - value = Core._apply_pure(f, args) - return Const(value) - catch - return nothing - end -end - function argtype_by_index(argtypes::Vector{Any}, i::Int) n = length(argtypes) na = argtypes[n] @@ -1457,9 +1492,12 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn # t, a = ti.parameters[i], argtypes′[i] # argtypes′[i] = t ⊑ a ? t : a # end - const_result = abstract_call_method_with_const_args(interp, result, singleton_type(ft′), arginfo, match, sv) - if const_result !== nothing - (;rt, const_result) = const_result + const_call_result = abstract_call_method_with_const_args(interp, result, singleton_type(ft′), arginfo, match, sv) + const_result = nothing + if const_call_result !== nothing + if const_call_result.rt ⊑ rt + (; rt, const_result) = const_call_result + end end return CallMeta(from_interprocedural!(rt, sv, arginfo, sig), InvokeCallInfo(match, const_result)) end @@ -1586,8 +1624,10 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif max_methods > 1 && istopfunction(f, :copyto!) max_methods = 1 elseif la == 3 && istopfunction(f, :typejoin) - val = pure_eval_call(f, argtypes) - return CallMeta(val === nothing ? Type : val, MethodResultPure()) + if is_all_const_arg(arginfo) + val = _pure_eval_call(f, arginfo) + return CallMeta(val === nothing ? Type : val, MethodResultPure()) + end end atype = argtypes_to_type(argtypes) return abstract_call_gf_by_type(interp, f, arginfo, atype, sv, max_methods) @@ -1602,10 +1642,12 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::Part match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) const_result = nothing if !result.edgecycle - const_result = abstract_call_method_with_const_args(interp, result, nothing, + const_call_result = abstract_call_method_with_const_args(interp, result, nothing, arginfo, match, sv) - if const_result !== nothing - (;rt, const_result) = const_result + if const_call_result !== nothing + if const_call_result.rt ⊑ rt + (; rt, const_result) = const_call_result + end end end info = OpaqueClosureCallInfo(match, const_result) @@ -2051,14 +2093,13 @@ end function handle_control_backedge!(frame::InferenceState, from::Int, to::Int) if from > to - def = frame.linfo.def - if isa(def, Method) - effects = decode_effects_override(def.purity) - if effects.terminates_globally || effects.terminates_locally - return nothing - end + if is_effect_overrided(frame, :terminates_globally) + # this frame is known to terminate + elseif is_effect_overrided(frame, :terminates_locally) + # this backedge is known to terminate + else + tristate_merge!(frame, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) end - tristate_merge!(frame, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) end return nothing end diff --git a/base/compiler/methodtable.jl b/base/compiler/methodtable.jl index 93020ae6a2639..70beb259cb6a5 100644 --- a/base/compiler/methodtable.jl +++ b/base/compiler/methodtable.jl @@ -84,9 +84,9 @@ function findall(@nospecialize(sig::Type), table::OverlayMethodTable; limit::Int _min_val[] = typemin(UInt) _max_val[] = typemax(UInt) ms = _methods_by_ftype(sig, nothing, limit, table.world, false, _min_val, _max_val, _ambig) - end - if ms === false - return missing + if ms === false + return missing + end end return MethodLookupResult(ms::Vector{Any}, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) end @@ -123,3 +123,8 @@ end # This query is not cached findsup(@nospecialize(sig::Type), table::CachedMethodTable) = findsup(sig, table.table) + +isoverlayed(::MethodTableView) = error("unsatisfied MethodTableView interface") +isoverlayed(::InternalMethodTable) = false +isoverlayed(::OverlayMethodTable) = true +isoverlayed(mt::CachedMethodTable) = isoverlayed(mt.table) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index a86e125fcb307..039d557b2e9e8 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1000,7 +1000,11 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr elseif isa(stmt, GotoNode) && compact.cfg_transforms_enabled result[result_idx][:inst] = GotoNode(compact.bb_rename_succ[stmt.label]) result_idx += 1 - elseif isa(stmt, GlobalRef) || isa(stmt, GotoNode) + elseif isa(stmt, GlobalRef) + result[result_idx][:inst] = stmt + result[result_idx][:type] = argextype(stmt, compact) + result_idx += 1 + elseif isa(stmt, GotoNode) result[result_idx][:inst] = stmt result_idx += 1 elseif isa(stmt, GotoIfNot) && compact.cfg_transforms_enabled diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index ec56b826d1491..54b89c9a04f43 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1705,7 +1705,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ end return false elseif f === Core.get_binding_type - return length(argtypes) == 2 + length(argtypes) == 2 || return false + return argtypes[1] ⊑ Module && argtypes[2] ⊑ Symbol elseif f === donotdelete return true end diff --git a/base/cpuid.jl b/base/cpuid.jl index b1fb82cf86dae..48930d8064ba9 100644 --- a/base/cpuid.jl +++ b/base/cpuid.jl @@ -56,9 +56,10 @@ const ISAs_by_family = Dict( "aarch64" => [ # Implicit in all sets, because always required: fp, asimd "armv8.0-a" => ISA(Set{UInt32}()), - "armv8.1-a" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm))), - "armv8.2-a+crypto" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2))), - "armv8.4-a+crypto+sve" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_fp16fml, JL_AArch64_aes, JL_AArch64_sha2, JL_AArch64_dotprod, JL_AArch64_sve))), + "armv8.1-a" => ISA(Set((JL_AArch64_v8_1a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm))), + "armv8.2-a+crypto" => ISA(Set((JL_AArch64_v8_2a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2))), + "a64fx" => ISA(Set((JL_AArch64_v8_2a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_sha2, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fullfp16, JL_AArch64_sve))), + "apple_m1" => ISA(Set((JL_AArch64_v8_5a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2, JL_AArch64_sha3, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fp16fml, JL_AArch64_fullfp16, JL_AArch64_dotprod, JL_AArch64_rcpc, JL_AArch64_altnzcv))), ], "powerpc64le" => [ # We have no way to test powerpc64le features yet, so we're only going to declare the lowest ISA: @@ -88,14 +89,27 @@ function normalize_arch(arch::String) return arch end +let + # Collect all relevant features for the current architecture, if any. + FEATURES = UInt32[] + arch = normalize_arch(String(Sys.ARCH)) + if arch in keys(ISAs_by_family) + for isa in ISAs_by_family[arch] + unique!(append!(FEATURES, last(isa).features)) + end + end + + # Use `@eval` to inline the list of features. + @eval function cpu_isa() + return ISA(Set{UInt32}(feat for feat in $(FEATURES) if test_cpu_feature(feat))) + end +end + """ cpu_isa() Return the [`ISA`](@ref) (instruction set architecture) of the current CPU. """ -function cpu_isa() - all_features = last(last(get(ISAs_by_family, normalize_arch(String(Sys.ARCH)), "" => [ISA(Set{UInt32}())]))).features - return ISA(Set{UInt32}(feat for feat in all_features if test_cpu_feature(feat))) -end +cpu_isa end # module CPUID diff --git a/base/process.jl b/base/process.jl index 16e66b0bd9884..6aac514191681 100644 --- a/base/process.jl +++ b/base/process.jl @@ -402,16 +402,25 @@ process failed, or if the process attempts to print anything to stdout. """ function open(f::Function, cmds::AbstractCmd, args...; kwargs...) P = open(cmds, args...; kwargs...) + function waitkill(P::Process) + close(P) + # 0.1 seconds after we hope it dies (from closing stdio), + # we kill the process with SIGTERM (15) + local t = Timer(0.1) do t + process_running(P) && kill(P) + end + wait(P) + close(t) + end ret = try f(P) catch - kill(P) - close(P) + waitkill(P) rethrow() end close(P.in) if !eof(P.out) - close(P.out) + waitkill(P) throw(_UVError("open(do)", UV_EPIPE)) end success(P) || pipeline_error(P) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index e6cf280812685..a10d195229cab 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -1,5 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +if Threads.nthreads() != 1 + @warn "Running this file with multiple Julia threads may lead to a build error" Threads.nthreads() +end + if Base.isempty(Base.ARGS) || Base.ARGS[1] !== "0" Sys.__init_build() # Prevent this from being put into the Main namespace diff --git a/doc/make.jl b/doc/make.jl index bb7ef83048178..f814ba43382e4 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -292,6 +292,7 @@ else analytics = "UA-28835595-6", collapselevel = 1, sidebar_sitename = false, + ansicolor = true, ) end diff --git a/doc/src/devdocs/EscapeAnalysis.md b/doc/src/devdocs/EscapeAnalysis.md index c4a5f14faa8ec..983a6782ccc79 100644 --- a/doc/src/devdocs/EscapeAnalysis.md +++ b/doc/src/devdocs/EscapeAnalysis.md @@ -1,3 +1,5 @@ +# `EscapeAnalysis` + `Core.Compiler.EscapeAnalysis` is a compiler utility module that aims to analyze escape information of [Julia's SSA-form IR](@ref Julia-SSA-form-IR) a.k.a. `IRCode`. @@ -18,8 +20,7 @@ This escape analysis aims to: You can give a try to the escape analysis by loading the `EAUtils.jl` utility script that define the convenience entries `code_escapes` and `@code_escapes` for testing and debugging purposes: ```@repl EAUtils -include(normpath(Sys.BINDIR::String, "..", "share", "julia", "test", "testhelpers", "EAUtils.jl")) -using EAUtils +include(normpath(Sys.BINDIR, "..", "share", "julia", "test", "compiler", "EscapeAnalysis", "EAUtils.jl")); using .EAUtils mutable struct SafeRef{T} x::T diff --git a/src/ccall.cpp b/src/ccall.cpp index 332c057afa5c4..1fc6e5d4d3ece 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -233,10 +233,11 @@ static GlobalVariable *emit_plt_thunk( else { // musttail support is very bad on ARM, PPC, PPC64 (as of LLVM 3.9) // Known failures includes vararg (not needed here) and sret. -#if (defined(_CPU_X86_) || defined(_CPU_X86_64_) || \ - defined(_CPU_AARCH64_)) + +#if (defined(_CPU_X86_) || defined(_CPU_X86_64_) || (defined(_CPU_AARCH64_) && !defined(_OS_DARWIN_))) // Ref https://bugs.llvm.org/show_bug.cgi?id=47058 // LLVM, as of 10.0.1 emits wrong/worse code when musttail is set + // Apple silicon macs give an LLVM ERROR if musttail is set here #44107. if (!attrs.hasAttrSomewhere(Attribute::ByVal)) ret->setTailCallKind(CallInst::TCK_MustTail); #endif diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index 42d67bd6f89c7..53105feb81bfe 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -227,7 +227,7 @@ class JITObjectRegistry continue; } } - uint64_t loadaddr = L.getSectionLoadAddress(section); + uint64_t loadaddr = getLoadAddress(section.getName().get()); size_t seclen = section.getSize(); if (istext) { arm_text_addr = loadaddr; diff --git a/src/gc-alloc-profiler.cpp b/src/gc-alloc-profiler.cpp index a5af9031cd0e3..818d6e803c9df 100644 --- a/src/gc-alloc-profiler.cpp +++ b/src/gc-alloc-profiler.cpp @@ -20,7 +20,7 @@ struct jl_raw_alloc_t { jl_datatype_t *type_address; jl_raw_backtrace_t backtrace; size_t size; - jl_task_t *task; + void *task; uint64_t timestamp; }; @@ -49,15 +49,24 @@ jl_combined_results g_combined_results; // Will live forever. // === stack stuff === jl_raw_backtrace_t get_raw_backtrace() JL_NOTSAFEPOINT { - // A single large buffer to record backtraces onto - static jl_bt_element_t static_bt_data[JL_MAX_BT_SIZE]; + // We first record the backtrace onto a MAX-sized buffer, so that we don't have to + // allocate the buffer until we know the size. To ensure thread-safety, we use a + // per-thread backtrace buffer. + jl_ptls_t ptls = jl_current_task->ptls; + jl_bt_element_t *shared_bt_data_buffer = ptls->profiling_bt_buffer; + if (shared_bt_data_buffer == NULL) { + size_t size = sizeof(jl_bt_element_t) * (JL_MAX_BT_SIZE + 1); + shared_bt_data_buffer = (jl_bt_element_t*) malloc_s(size); + ptls->profiling_bt_buffer = shared_bt_data_buffer; + } - size_t bt_size = rec_backtrace(static_bt_data, JL_MAX_BT_SIZE, 2); + size_t bt_size = rec_backtrace(shared_bt_data_buffer, JL_MAX_BT_SIZE, 2); // Then we copy only the needed bytes out of the buffer into our profile. size_t bt_bytes = bt_size * sizeof(jl_bt_element_t); - jl_bt_element_t *bt_data = (jl_bt_element_t*) malloc(bt_bytes); - memcpy(bt_data, static_bt_data, bt_bytes); + jl_bt_element_t *bt_data = (jl_bt_element_t*) malloc_s(bt_bytes); + memcpy(bt_data, shared_bt_data_buffer, bt_bytes); + return jl_raw_backtrace_t{ bt_data, @@ -71,7 +80,7 @@ extern "C" { // Needed since these functions doesn't take any arguments. JL_DLLEXPORT void jl_start_alloc_profile(double sample_rate) { // We only need to do this once, the first time this is called. - while (g_alloc_profile.per_thread_profiles.size() < jl_n_threads) { + while (g_alloc_profile.per_thread_profiles.size() < (size_t)jl_n_threads) { g_alloc_profile.per_thread_profiles.push_back(jl_per_thread_alloc_profile_t{}); } @@ -135,7 +144,7 @@ void _maybe_record_alloc_to_profile(jl_value_t *val, size_t size, jl_datatype_t type, get_raw_backtrace(), size, - jl_current_task, + (void *)jl_current_task, cycleclock() }); } diff --git a/src/gf.c b/src/gf.c index 01d03fe77394f..25b29c63a0782 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1961,6 +1961,8 @@ jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi, size_t world) return NULL; } +jl_mutex_t precomp_statement_out_lock; + static void record_precompile_statement(jl_method_instance_t *mi) { static ios_t f_precompile; @@ -1971,6 +1973,8 @@ static void record_precompile_statement(jl_method_instance_t *mi) if (!jl_is_method(def)) return; + if (jl_n_threads > 1) + JL_LOCK(&precomp_statement_out_lock); if (s_precompile == NULL) { const char *t = jl_options.trace_compile; if (!strncmp(t, "stderr", 6)) { @@ -1989,6 +1993,8 @@ static void record_precompile_statement(jl_method_instance_t *mi) if (s_precompile != JL_STDERR) ios_flush(&f_precompile); } + if (jl_n_threads > 1) + JL_UNLOCK(&precomp_statement_out_lock); } jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t world) diff --git a/src/jloptions.c b/src/jloptions.c index 1ff4da7c5c10b..b85e8f7bfe386 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -4,6 +4,7 @@ #include #include "julia.h" +#include "julia_internal.h" #include #include diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index caeca92f75803..52bcb307ebe19 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -120,6 +120,10 @@ ;; inside ref only replace within the first argument (list* 'ref (replace-beginend (cadr ex) a n tuples last) (cddr ex))) + ;; TODO: this probably should not be allowed since keyword args aren't + ;; positional, but in this context we have just used their positions anyway + ((eq? (car ex) 'kw) + (list 'kw (cadr ex) (replace-beginend (caddr ex) a n tuples last))) (else (cons (car ex) (map (lambda (x) (replace-beginend x a n tuples last)) @@ -142,16 +146,20 @@ (idx (if (vararg? idx0) (cadr idx0) idx0)) (last (null? (cdr lst))) (replaced (replace-beginend idx a n tuples last)) - (idx (if (or (not has-va?) (simple-atom? replaced)) replaced (make-ssavalue)))) + (val (if (kwarg? replaced) (caddr replaced) replaced)) + (idx (if (or (not has-va?) (simple-atom? val)) + val (make-ssavalue)))) (loop (cdr lst) (+ n 1) - (if (eq? idx replaced) + (if (eq? idx val) stmts - (cons `(= ,idx ,replaced) + (cons `(= ,idx ,val) stmts)) (if (vararg? idx0) (cons idx tuples) tuples) (cons (if (vararg? idx0) `(... ,idx) - idx) + (if (eq? val replaced) + idx + (list 'kw (cadr replaced) idx))) ret))))))) ;; GF method does not need to keep decl expressions on lambda args diff --git a/src/julia_threads.h b/src/julia_threads.h index 371eb51250115..22acf3aec8587 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -246,6 +246,8 @@ typedef struct _jl_tls_states_t { // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. struct _jl_bt_element_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long size_t bt_size; // Size for backtrace in transit in bt_data + // Temporary backtrace buffer used only for allocations profiler. + struct _jl_bt_element_t *profiling_bt_buffer; // Atomically set by the sender, reset by the handler. volatile _Atomic(sig_atomic_t) signal_request; // TODO: no actual reason for this to be _Atomic // Allow the sigint to be raised asynchronously diff --git a/src/signal-handling.c b/src/signal-handling.c index 142f03b6c899d..d9c53b0211eae 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -132,22 +132,23 @@ static size_t jl_safe_read_mem(const volatile char *ptr, char *out, size_t len) static double profile_autostop_time = -1.0; static double profile_peek_duration = 1.0; // seconds -double jl_get_profile_peek_duration(void) { +double jl_get_profile_peek_duration(void) +{ return profile_peek_duration; } -void jl_set_profile_peek_duration(double t) { +void jl_set_profile_peek_duration(double t) +{ profile_peek_duration = t; - return; } uintptr_t profile_show_peek_cond_loc; JL_DLLEXPORT void jl_set_peek_cond(uintptr_t cond) { profile_show_peek_cond_loc = cond; - return; } -static void jl_check_profile_autostop(void) { +static void jl_check_profile_autostop(void) +{ if ((profile_autostop_time != -1.0) && (jl_hrtime() > profile_autostop_time)) { profile_autostop_time = -1.0; jl_profile_stop_timer(); diff --git a/src/signals-unix.c b/src/signals-unix.c index b2e3ef2952029..b4eb57a5501cb 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -501,6 +501,16 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) errno = errno_save; } +// Because SIGUSR1 is dual-purpose, and the timer can have trailing signals after being deleted, +// a 2-second grace period is imposed to ignore any trailing timer-created signals so they don't get +// confused for user triggers +uint64_t last_timer_delete_time = 0; + +int timer_graceperiod_elapsed(void) +{ + return jl_hrtime() > (last_timer_delete_time + 2e9); +} + #if defined(HAVE_TIMER) // Linux-style #include @@ -539,9 +549,11 @@ JL_DLLEXPORT int jl_profile_start_timer(void) JL_DLLEXPORT void jl_profile_stop_timer(void) { - if (running) + if (running) { timer_delete(timerprof); - running = 0; + last_timer_delete_time = jl_hrtime(); + running = 0; + } } #elif defined(HAVE_ITIMER) @@ -556,18 +568,22 @@ JL_DLLEXPORT int jl_profile_start_timer(void) timerprof.it_interval.tv_usec = 0; timerprof.it_value.tv_sec = nsecprof / GIGA; timerprof.it_value.tv_usec = ((nsecprof % GIGA) + 999) / 1000; - if (setitimer(ITIMER_PROF, &timerprof, NULL) == -1) - return -3; + // Because SIGUSR1 is multipurpose, set `running` before so that we know that the first SIGUSR1 came from the timer running = 1; + if (setitimer(ITIMER_PROF, &timerprof, NULL) == -1) { + running = 0; + return -3; + } return 0; } JL_DLLEXPORT void jl_profile_stop_timer(void) { if (running) { - running = 0; memset(&timerprof, 0, sizeof(timerprof)); setitimer(ITIMER_PROF, &timerprof, NULL); + last_timer_delete_time = jl_hrtime(); + running = 0; } } @@ -776,7 +792,7 @@ static void *signal_listener(void *arg) } #else if (sig == SIGUSR1) { - if (running != 1) + if (running != 1 && timer_graceperiod_elapsed()) trigger_profile_peek(); doexit = 0; } diff --git a/stdlib/LinearAlgebra/src/blas.jl b/stdlib/LinearAlgebra/src/blas.jl index e8d44c1ae1533..9b5506e993ea8 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -1216,7 +1216,7 @@ end """ spr!(uplo, α, x, AP) -Update matrix `A` as `α*A*x*x'`, where `A` is a symmetric matrix provided +Update matrix `A` as `A+α*x*x'`, where `A` is a symmetric matrix provided in packed format `AP` and `x` is a vector. With `uplo = 'U'`, the array AP must contain the upper triangular part of the diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 5565b28ebabe7..676e965652e8f 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -886,7 +886,9 @@ function dot(x::AbstractArray, y::AbstractArray) s end -dot(x::Adjoint, y::Adjoint) = conj(dot(parent(x), parent(y))) +function dot(x::Adjoint{<:Union{Real,Complex}}, y::Adjoint{<:Union{Real,Complex}}) + return conj(dot(parent(x), parent(y))) +end dot(x::Transpose, y::Transpose) = dot(parent(x), parent(y)) """ diff --git a/stdlib/LinearAlgebra/test/generic.jl b/stdlib/LinearAlgebra/test/generic.jl index 26534a2cdf0cd..b56edf9439fe0 100644 --- a/stdlib/LinearAlgebra/test/generic.jl +++ b/stdlib/LinearAlgebra/test/generic.jl @@ -497,20 +497,21 @@ end end @testset "adjtrans dot" begin - for t in (transpose, adjoint) - x, y = t(rand(ComplexF64, 10)), t(rand(ComplexF64, 10)) + for t in (transpose, adjoint), T in (ComplexF64, Quaternion{Float64}) + x, y = t(rand(T, 10)), t(rand(T, 10)) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x, y = t([rand(ComplexF64, 2, 2) for _ in 1:5]), t([rand(ComplexF64, 2, 2) for _ in 1:5]) + x, y = t([rand(T, 2, 2) for _ in 1:5]), t([rand(T, 2, 2) for _ in 1:5]) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x, y = t(rand(ComplexF64, 10, 5)), t(rand(ComplexF64, 10, 5)) + x, y = t(rand(T, 10, 5)), t(rand(T, 10, 5)) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x = t([rand(ComplexF64, 2, 2) for _ in 1:5, _ in 1:5]) - y = t([rand(ComplexF64, 2, 2) for _ in 1:5, _ in 1:5]) + x = t([rand(T, 2, 2) for _ in 1:5, _ in 1:5]) + y = t([rand(T, 2, 2) for _ in 1:5, _ in 1:5]) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) + x, y = t([rand(T, 2, 2) for _ in 1:5]), t([rand(T, 2, 2) for _ in 1:5]) end end diff --git a/stdlib/Profile/Project.toml b/stdlib/Profile/Project.toml index 1d13dad22233a..334d475832b6d 100644 --- a/stdlib/Profile/Project.toml +++ b/stdlib/Profile/Project.toml @@ -5,9 +5,10 @@ uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" [extras] +Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Logging", "Serialization", "Test"] +test = ["Base64", "Logging", "Serialization", "Test"] diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index aa689936d4598..26dd90a821e01 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -123,7 +123,7 @@ struct Alloc type::Any stacktrace::StackTrace size::Int - task::Ptr{Cvoid} + task::Ptr{Cvoid} # N.B. unrooted, may not be valid timestamp::UInt64 end diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 50e03d2c79a5a..b11dfb488c373 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -54,7 +54,7 @@ function _peek_report() iob = IOBuffer() ioc = IOContext(IOContext(iob, stdout), :displaysize=>displaysize(stdout)) print(ioc, groupby = [:thread, :task]) - Base.print(stdout, String(resize!(iob.data, iob.size))) + Base.print(stdout, String(take!(iob))) end # This is a ref so that it can be overridden by other profile info consumers. const peek_report = Ref{Function}(_peek_report) @@ -73,7 +73,8 @@ Set the duration in seconds of the profile "peek" that is triggered via `SIGINFO set_peek_duration(t::Float64) = ccall(:jl_set_profile_peek_duration, Cvoid, (Float64,), t) precompile_script = """ -Profile.@profile sleep(0.5) +import Profile +Profile.@profile while Profile.len_data() < 1000; rand(10,10) * rand(10,10); end Profile.peek_report[]() Profile.clear() """ diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 50917b9797c7d..c6f78cbed0522 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -200,9 +200,12 @@ if Sys.isbsd() || Sys.islinux() """ iob = Base.BufferStream() p = run(pipeline(`$cmd -e $script`, stderr = devnull, stdout = iob), wait = false) - t = Timer(60) do t # should be done in under 10 seconds + t = Timer(120) do t + # should be under 10 seconds, so give it 2 minutes then report failure + println("KILLING BY PROFILE TEST WATCHDOG\n") + kill(p, Base.SIGTERM) + sleep(10) kill(p, Base.SIGKILL) - sleep(5) close(iob) end try @@ -210,7 +213,7 @@ if Sys.isbsd() || Sys.islinux() @assert occursin("started", s) @assert process_running(p) for _ in 1:2 - sleep(2) + sleep(2.5) if Sys.isbsd() kill(p, 29) # SIGINFO elseif Sys.islinux() @@ -226,8 +229,6 @@ if Sys.isbsd() || Sys.islinux() end end end -else - @warn "Skipping \"SIGINFO/SIGUSR1 profile triggering\" test as it is not supported on this platform" end @testset "FlameGraphs" begin diff --git a/stdlib/Sockets/test/runtests.jl b/stdlib/Sockets/test/runtests.jl index 6b8b1be6e055f..a27bb89408f1d 100644 --- a/stdlib/Sockets/test/runtests.jl +++ b/stdlib/Sockets/test/runtests.jl @@ -16,7 +16,7 @@ function killjob(d) end if @isdefined(SIGINFO) ccall(:uv_kill, Cint, (Cint, Cint), getpid(), SIGINFO) - sleep(1) + sleep(5) # Allow time for profile to collect and print before killing end ccall(:uv_kill, Cint, (Cint, Cint), getpid(), Base.SIGTERM) nothing diff --git a/sysimage.mk b/sysimage.mk index de5c3e22f253a..1586eb7dbc16d 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -77,6 +77,7 @@ define sysimg_builder $$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(build_private_libdir)/sys$1-%.a : $$(build_private_libdir)/sys.ji @$$(call PRINT_JULIA, cd $$(JULIAHOME)/base && \ if ! JULIA_BINDIR=$$(call cygpath_w,$(build_bindir)) WINEPATH="$$(call cygpath_w,$$(build_bindir));$$$$WINEPATH" \ + JULIA_NUM_THREADS=1 \ $$(call spawn, $3) $2 -C "$$(JULIA_CPU_TARGET)" --output-$$* $$(call cygpath_w,$$@).tmp $$(JULIA_SYSIMG_BUILD_FLAGS) \ --startup-file=no --warn-overwrite=yes --sysimage $$(call cygpath_w,$$<) $$(call cygpath_w,$$(JULIAHOME)/contrib/generate_precompile.jl) $(JULIA_PRECOMPILE); then \ echo '*** This error is usually fixed by running `make clean`. If the error persists$$(COMMA) try `make cleanall`. ***'; \ diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 060f1ffa8b8cb..d650cf67ebf11 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -732,6 +732,7 @@ function test_cat(::Type{TestAbstractArray}) @test @inferred(cat(As...; dims=Val(3))) == zeros(2, 2, 2) cat3v(As) = cat(As...; dims=Val(3)) @test @inferred(cat3v(As)) == zeros(2, 2, 2) + @test @inferred(cat(As...; dims=Val((1,2)))) == zeros(4, 4) end function test_ind2sub(::Type{TestAbstractArray}) diff --git a/test/arrayops.jl b/test/arrayops.jl index 84b0e7d259f45..b2badb66ce93d 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2318,10 +2318,12 @@ let A = zeros(Int, 2, 2), B = zeros(Float64, 2, 2) f40() = Float64[A A] f41() = [A B] f42() = Int[A B] + f43() = Int[A...] + f44() = Float64[A..., B...] for f in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, - f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, f42] + f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, f42, f43, f44] @test isconcretetype(Base.return_types(f, ())[1]) end end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d4fbe0dbfbf6b..61058f9589f52 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4028,3 +4028,5 @@ function f_boundscheck_elim(n) ntuple(x->(@inbounds getfield(sin, x)), n) end @test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] + +@test !Core.Compiler.builtin_nothrow(Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any) diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 9347acc83f13f..3259c752d9aa0 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1069,3 +1069,33 @@ end @test fully_eliminated() do issue41694(2) end + +Base.@assume_effects :terminates_globally function recur_termination1(x) + x == 1 && return 1 + 1 < x < 20 || throw("bad") + return x * recur_termination1(x-1) +end +@test fully_eliminated() do + recur_termination1(12) +end +Base.@assume_effects :terminates_globally function recur_termination21(x) + x == 1 && return 1 + 1 < x < 20 || throw("bad") + return recur_termination22(x) +end +recur_termination22(x) = x * recur_termination21(x-1) +@test fully_eliminated() do + recur_termination21(12) + recur_termination22(12) +end + +global x44200::Int = 0 +function f44200() + global x = 0 + while x < 10 + x += 1 + end + x +end +let src = code_typed1(f44200) + @test count(x -> isa(x, Core.PiNode), src.code) == 0 +end diff --git a/test/dict.jl b/test/dict.jl index cbbb475c993fd..ad15764a09d16 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -1179,6 +1179,8 @@ end @test s === copy!(s, Base.ImmutableDict(a[])) == Dict(a[]) end end + s2 = copy(s) + @test copy!(s, s) == s2 end @testset "map!(f, values(dict))" begin diff --git a/test/sets.jl b/test/sets.jl index 43ba433e780d7..1999dbf3d020f 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -151,6 +151,9 @@ end @test s === copy!(s, BitSet(a)) == S(a) end end + s = Set([1, 2]) + s2 = copy(s) + @test copy!(s, s) == s2 end @testset "sizehint, empty" begin diff --git a/test/spawn.jl b/test/spawn.jl index 92232ba5d70f6..a8a2af40643ff 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -826,6 +826,12 @@ end dir = joinpath(pwd(), "dir") cmd = addenv(setenv(`julia`; dir=dir), Dict()) @test cmd.dir == dir + + @test addenv(``, ["a=b=c"], inherit=false).env == ["a=b=c"] + cmd = addenv(``, "a"=>"b=c", inherit=false) + @test cmd.env == ["a=b=c"] + cmd = addenv(cmd, "b"=>"b") + @test issetequal(cmd.env, ["b=b", "a=b=c"]) end @testset "setenv with dir (with tests for #42131)" begin diff --git a/test/syntax.jl b/test/syntax.jl index 69d3e8c7fe591..ff65cb235d92c 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1212,6 +1212,17 @@ let a = [], b = [4,3,2,1] @test a == [1,2] end +# issue #44239 +struct KWGetindex end +Base.getindex(::KWGetindex, args...; kws...) = (args, NamedTuple(kws)) +let A = KWGetindex(), a = [], b = [4,3,2,1] + f() = (push!(a, 1); 2) + g() = (push!(a, 2); ()) + @test A[f(), g()..., k = f()] === ((2,), (k = 2,)) + @test a == [1, 2, 1] + @test A[var"end"=1] === ((), (var"end" = 1,)) +end + @testset "raw_str macro" begin @test raw"$" == "\$" @test raw"\n" == "\\n" diff --git a/test/testhelpers/Quaternions.jl b/test/testhelpers/Quaternions.jl index a3967c1aacc43..1eddad322ec40 100644 --- a/test/testhelpers/Quaternions.jl +++ b/test/testhelpers/Quaternions.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license module Quaternions +using Random export Quaternion @@ -36,4 +37,17 @@ Base.:(*)(q::Quaternion, b::Bool) = b * q # remove method ambiguity Base.:(/)(q::Quaternion, w::Quaternion) = q * conj(w) * (1.0 / abs2(w)) Base.:(\)(q::Quaternion, w::Quaternion) = conj(q) * w * (1.0 / abs2(q)) +# adapted from https://github.com/JuliaGeometry/Quaternions.jl/pull/42 +function Base.rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real} + return Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T)) +end +function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractFloat} + return Quaternion{T}( + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + ) +end + end diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 1b146f48e8c57..63e3be1b88cb7 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -16,7 +16,7 @@ function killjob(d) end if @isdefined(SIGINFO) ccall(:uv_kill, Cint, (Cint, Cint), getpid(), SIGINFO) - sleep(1) + sleep(5) # Allow time for profile to collect and print before killing end ccall(:uv_kill, Cint, (Cint, Cint), getpid(), Base.SIGTERM) nothing