diff --git a/Make.inc b/Make.inc index d315dfd0c6742..424303e987256 100644 --- a/Make.inc +++ b/Make.inc @@ -21,6 +21,9 @@ JULIA_PRECOMPILE ?= 1 # and LLVM_ASSERTIONS=1. FORCE_ASSERTIONS ?= 0 +# Set BOOTSTRAP_DEBUG_LEVEL to 1 to enable Julia-level stacktrace during bootstrapping. +BOOTSTRAP_DEBUG_LEVEL ?= 0 + # OPENBLAS build options OPENBLAS_TARGET_ARCH:= OPENBLAS_SYMBOLSUFFIX:= diff --git a/base/Base.jl b/base/Base.jl index ed50dd7a21bc2..42e740d15916c 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -277,6 +277,7 @@ include("condition.jl") include("threads.jl") include("lock.jl") include("channels.jl") +include("partr.jl") include("task.jl") include("threads_overloads.jl") include("weakkeydict.jl") diff --git a/base/boot.jl b/base/boot.jl index d797e0a815a81..c994a491f438c 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -224,7 +224,7 @@ primitive type Char <: AbstractChar 32 end primitive type Int8 <: Signed 8 end #primitive type UInt8 <: Unsigned 8 end primitive type Int16 <: Signed 16 end -primitive type UInt16 <: Unsigned 16 end +#primitive type UInt16 <: Unsigned 16 end #primitive type Int32 <: Signed 32 end #primitive type UInt32 <: Unsigned 32 end #primitive type Int64 <: Signed 64 end diff --git a/base/client.jl b/base/client.jl index 842389224637f..d335f378f24c1 100644 --- a/base/client.jl +++ b/base/client.jl @@ -124,14 +124,14 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) end if lasterr !== nothing lasterr = scrub_repl_backtrace(lasterr) - istrivialerror(lasterr) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) + istrivialerror(lasterr) || setglobal!(Main, :err, lasterr) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing else ast = Meta.lower(Main, ast) value = Core.eval(Main, ast) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value) + setglobal!(Main, :ans, value) if !(value === nothing) && show_value if have_color print(answer_color()) @@ -151,7 +151,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) end errcount += 1 lasterr = scrub_repl_backtrace(current_exceptions()) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) + setglobal!(Main, :err, lasterr) if errcount > 2 @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount break diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 3e34528aa8284..eae484b739ad9 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1503,25 +1503,26 @@ end function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::InferenceState) ft′ = argtype_by_index(argtypes, 2) ft = widenconst(ft′) - ft === Bottom && return CallMeta(Bottom, false) + ft === Bottom && return CallMeta(Bottom, false), EFFECTS_THROWN (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3)) - types === Bottom && return CallMeta(Bottom, false) - isexact || return CallMeta(Any, false) + types === Bottom && return CallMeta(Bottom, false), EFFECTS_THROWN + isexact || return CallMeta(Any, false), Effects() argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, false) - nargtype isa DataType || return CallMeta(Any, false) # other cases are not implemented below - isdispatchelem(ft) || return CallMeta(Any, false) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + nargtype === Bottom && return CallMeta(Bottom, false), EFFECTS_THROWN + nargtype isa DataType || return CallMeta(Any, false), Effects() # other cases are not implemented below + isdispatchelem(ft) || return CallMeta(Any, false), Effects() # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below ft = ft::DataType types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} match, valid_worlds, overlayed = findsup(types, method_table(interp)) - match === nothing && return CallMeta(Any, false) + match === nothing && return CallMeta(Any, false), Effects() update_valid_age!(sv, valid_worlds) method = match.method (ti, env::SimpleVector) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector (; rt, edge) = result = abstract_call_method(interp, method, ti, env, false, sv) + effects = result.edge_effects edge !== nothing && add_backedge!(edge::MethodInstance, sv) match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing @@ -1539,10 +1540,10 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn const_result = nothing if const_call_result !== nothing if const_call_result.rt ⊑ rt - (; rt, const_result) = const_call_result + (; rt, effects, const_result) = const_call_result end end - return CallMeta(from_interprocedural!(rt, sv, arginfo, sig), InvokeCallInfo(match, const_result)) + return CallMeta(from_interprocedural!(rt, sv, arginfo, sig), InvokeCallInfo(match, const_result)), effects end function invoke_rewrite(xs::Vector{Any}) @@ -1563,14 +1564,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), if f === _apply_iterate return abstract_apply(interp, argtypes, sv, max_methods) elseif f === invoke - call = abstract_invoke(interp, arginfo, sv) - if call.info === false - if call.rt === Bottom - tristate_merge!(sv, Effects(EFFECTS_TOTAL; nothrow=ALWAYS_FALSE)) - else - tristate_merge!(sv, Effects()) - end - end + call, effects = abstract_invoke(interp, arginfo, sv) + tristate_merge!(sv, effects) return call elseif f === modifyfield! tristate_merge!(sv, Effects()) # TODO diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 7fcaa79a468d5..9a0468e8e36ea 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -6,28 +6,44 @@ function is_known_call(@nospecialize(x), @nospecialize(func), ir::Union{IRCode,I return singleton_type(ft) === func end +struct SSAUse + kind::Symbol + idx::Int +end +GetfieldUse(idx::Int) = SSAUse(:getfield, idx) +PreserveUse(idx::Int) = SSAUse(:preserve, idx) +NoPreserve() = SSAUse(:nopreserve, 0) +IsdefinedUse(idx::Int) = SSAUse(:isdefined, idx) + """ du::SSADefUse This struct keeps track of all uses of some mutable struct allocated in the current function: -- `du.uses::Vector{Int}` are all instances of `getfield` on the struct +- `du.uses::Vector{SSAUse}` are some "usages" (like `getfield`) of the struct - `du.defs::Vector{Int}` are all instances of `setfield!` on the struct The terminology refers to the uses/defs of the "slot bundle" that the mutable struct represents. -In addition we keep track of all instances of a `:foreigncall` that preserves of this mutable -struct in `du.ccall_preserve_uses`. Somewhat counterintuitively, we don't actually need to -make sure that the struct itself is live (or even allocated) at a `ccall` site. -If there are no other places where the struct escapes (and thus e.g. where its address is taken), -it need not be allocated. We do however, need to make sure to preserve any elements of this struct. +`du.uses` tracks all instances of `getfield` and `isdefined` calls on the struct. +Additionally it also tracks all instances of a `:foreigncall` that preserves of this mutable +struct. Somewhat counterintuitively, we don't actually need to make sure that the struct +itself is live (or even allocated) at a `ccall` site. If there are no other places where +the struct escapes (and thus e.g. where its address is taken), it need not be allocated. +We do however, need to make sure to preserve any elements of this struct. """ struct SSADefUse - uses::Vector{Int} + uses::Vector{SSAUse} defs::Vector{Int} - ccall_preserve_uses::Vector{Int} end -SSADefUse() = SSADefUse(Int[], Int[], Int[]) +SSADefUse() = SSADefUse(SSAUse[], Int[]) -compute_live_ins(cfg::CFG, du::SSADefUse) = compute_live_ins(cfg, du.defs, du.uses) +function compute_live_ins(cfg::CFG, du::SSADefUse) + uses = Int[] + for use in du.uses + use.kind === :isdefined && continue # filter out `isdefined` usages + push!(uses, use.idx) + end + compute_live_ins(cfg, du.defs, uses) +end # assume `stmt == getfield(obj, field, ...)` or `stmt == setfield!(obj, field, val, ...)` try_compute_field_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr) = @@ -86,7 +102,8 @@ function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector def == 0 ? phinodes[curblock] : val_for_def_expr(ir, def, fidx) end -function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int) +function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, + du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int) def, useblock, curblock = find_def_for_use(ir, domtree, allblocks, du, use) if def == 0 if !haskey(phinodes, curblock) @@ -355,10 +372,7 @@ function lift_leaves(compact::IncrementalCompact, lift_arg!(compact, leaf, cache_key, def, 1+field, lifted_leaves) continue elseif isexpr(def, :new) - typ = widenconst(types(compact)[leaf]) - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(widenconst(types(compact)[leaf])) (isa(typ, DataType) && !isabstracttype(typ)) || return nothing @assert !ismutabletype(typ) if length(def.args) < 1+field @@ -725,7 +739,7 @@ function sroa_pass!(ir::IRCode) for ((_, idx), stmt) in compact # check whether this statement is `getfield` / `setfield!` (or other "interesting" statement) isa(stmt, Expr) || continue - is_setfield = false + is_setfield = is_isdefined = false field_ordering = :unspecified if is_known_call(stmt, setfield!, compact) 4 <= length(stmt.args) <= 5 || continue @@ -741,6 +755,13 @@ function sroa_pass!(ir::IRCode) field_ordering = argextype(stmt.args[4], compact) widenconst(field_ordering) === Bool && (field_ordering = :unspecified) end + elseif is_known_call(stmt, isdefined, compact) + 3 <= length(stmt.args) <= 4 || continue + is_isdefined = true + if length(stmt.args) == 4 + field_ordering = argextype(stmt.args[4], compact) + widenconst(field_ordering) === Bool && (field_ordering = :unspecified) + end elseif isexpr(stmt, :foreigncall) nccallargs = length(stmt.args[3]::SimpleVector) preserved = Int[] @@ -762,10 +783,7 @@ function sroa_pass!(ir::IRCode) push!(preserved, preserved_arg.id) continue elseif isexpr(def, :new) - typ = widenconst(argextype(SSAValue(defidx), compact)) - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(widenconst(argextype(SSAValue(defidx), compact))) if typ isa DataType && !ismutabletype(typ) record_immutable_preserve!(new_preserves, def, compact) push!(preserved, preserved_arg.id) @@ -777,8 +795,8 @@ function sroa_pass!(ir::IRCode) if defuses === nothing defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}() end - mid, defuse = get!(defuses, defidx, (SPCSet(), SSADefUse())) - push!(defuse.ccall_preserve_uses, idx) + mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, defidx) + push!(defuse.uses, PreserveUse(idx)) union!(mid, intermediaries) end continue @@ -795,13 +813,11 @@ function sroa_pass!(ir::IRCode) lift_comparison!(===, compact, idx, stmt, lifting_cache) elseif is_known_call(stmt, isa, compact) lift_comparison!(isa, compact, idx, stmt, lifting_cache) - elseif is_known_call(stmt, isdefined, compact) - lift_comparison!(isdefined, compact, idx, stmt, lifting_cache) end continue end - # analyze this `getfield` / `setfield!` call + # analyze this `getfield` / `isdefined` / `setfield!` call field = try_compute_field_stmt(compact, stmt) field === nothing && continue @@ -812,10 +828,15 @@ function sroa_pass!(ir::IRCode) if isa(struct_typ, Union) && struct_typ <: Tuple struct_typ = unswitchtupleunion(struct_typ) end + if isa(struct_typ, Union) && is_isdefined + lift_comparison!(isdefined, compact, idx, stmt, lifting_cache) + continue + end isa(struct_typ, DataType) || continue struct_typ.name.atomicfields == C_NULL || continue # TODO: handle more - if !(field_ordering === :unspecified || (field_ordering isa Const && field_ordering.val === :not_atomic)) + if !((field_ordering === :unspecified) || + (field_ordering isa Const && field_ordering.val === :not_atomic)) continue end @@ -833,17 +854,21 @@ function sroa_pass!(ir::IRCode) if defuses === nothing defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}() end - mid, defuse = get!(defuses, def.id, (SPCSet(), SSADefUse())) + mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, def.id) if is_setfield push!(defuse.defs, idx) + elseif is_isdefined + push!(defuse.uses, IsdefinedUse(idx)) else - push!(defuse.uses, idx) + push!(defuse.uses, GetfieldUse(idx)) end union!(mid, intermediaries) end continue elseif is_setfield continue # invalid `setfield!` call, but just ignore here + elseif is_isdefined + continue # TODO? end # perform SROA on immutable structs here on @@ -906,7 +931,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # escapes and we cannot eliminate the allocation. This works, because we're guaranteed # not to include any intermediaries that have dead uses. As a result, missing uses will only ever # show up in the nuses_total count. - nleaves = length(defuse.uses) + length(defuse.defs) + length(defuse.ccall_preserve_uses) + nleaves = length(defuse.uses) + length(defuse.defs) nuses = 0 for idx in intermediaries nuses += used_ssas[idx] @@ -917,19 +942,22 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse defexpr = ir[SSAValue(idx)][:inst] isexpr(defexpr, :new) || continue newidx = idx - typ = ir.stmts[newidx][:type] - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(ir.stmts[newidx][:type]) # Could still end up here if we tried to setfield! on an immutable, which would # error at runtime, but is not illegal to have in the IR. ismutabletype(typ) || continue typ = typ::DataType # Partition defuses by field fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)] - all_forwarded = true + all_eliminated = all_forwarded = true for use in defuse.uses - stmt = ir[SSAValue(use)][:inst] # == `getfield` call + if use.kind === :preserve + for du in fielddefuse + push!(du.uses, use) + end + continue + end + stmt = ir[SSAValue(use.idx)][:inst] # == `getfield`/`isdefined` call # We may have discovered above that this use is dead # after the getfield elim of immutables. In that case, # it would have been deleted. That's fine, just ignore @@ -968,8 +996,30 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse allblocks = sort(vcat(phiblocks, ldu.def_bbs)) blocks[fidx] = phiblocks, allblocks if fidx + 1 > length(defexpr.args) - for use in du.uses - has_safe_def(ir, get_domtree(), allblocks, du, newidx, use) || @goto skip + for i = 1:length(du.uses) + use = du.uses[i] + if use.kind === :isdefined + if has_safe_def(ir, get_domtree(), allblocks, du, newidx, use.idx) + ir[SSAValue(use.idx)][:inst] = true + else + all_eliminated = false + end + continue + elseif use.kind === :preserve + if length(du.defs) == 1 # allocation with this field unintialized + # there is nothing to preserve, just ignore this use + du.uses[i] = NoPreserve() + continue + end + end + has_safe_def(ir, get_domtree(), allblocks, du, newidx, use.idx) || @goto skip + end + else # always have some definition at the allocation site + for i = 1:length(du.uses) + use = du.uses[i] + if use.kind === :isdefined + ir[SSAValue(use.idx)][:inst] = true + end end end end @@ -978,8 +1028,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # This needs to be after we iterate through the IR with `IncrementalCompact` # because removing dead blocks can invalidate the domtree. domtree = get_domtree() - preserve_uses = isempty(defuse.ccall_preserve_uses) ? nothing : - IdDict{Int, Vector{Any}}((idx=>Any[] for idx in SPCSet(defuse.ccall_preserve_uses))) + local preserve_uses = nothing for fidx in 1:ndefuse du = fielddefuse[fidx] ftyp = fieldtype(typ, fidx) @@ -991,14 +1040,25 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse NewInstruction(PhiNode(), ftyp)) end # Now go through all uses and rewrite them - for stmt in du.uses - ir[SSAValue(stmt)][:inst] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, stmt) - end - if !isbitstype(ftyp) - if preserve_uses !== nothing - for (use, list) in preserve_uses - push!(list, compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use)) + for use in du.uses + if use.kind === :getfield + ir[SSAValue(use.idx)][:inst] = compute_value_for_use(ir, domtree, allblocks, + du, phinodes, fidx, use.idx) + elseif use.kind === :isdefined + continue # already rewritten if possible + elseif use.kind === :nopreserve + continue # nothing to preserve (may happen when there are unintialized fields) + elseif use.kind === :preserve + newval = compute_value_for_use(ir, domtree, allblocks, + du, phinodes, fidx, use.idx) + if !isbitstype(widenconst(argextype(newval, ir))) + if preserve_uses === nothing + preserve_uses = IdDict{Int, Vector{Any}}() + end + push!(get!(()->Any[], preserve_uses, use.idx), newval) end + else + @assert false "sroa_mutables!: unexpected use" end end for b in phiblocks @@ -1010,6 +1070,10 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse end end end + all_eliminated || continue + # all "usages" (i.e. `getfield` and `isdefined` calls) are eliminated, + # now eliminate "definitions" (`setfield!`) calls + # (NOTE the allocation itself will be eliminated by DCE pass later) for stmt in du.defs stmt == newidx && continue ir[SSAValue(stmt)][:inst] = nothing @@ -1023,8 +1087,9 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse push!(intermediaries, newidx) end # Insert the new preserves - for (use, new_preserves) in preserve_uses - ir[SSAValue(use)][:inst] = form_new_preserves(ir[SSAValue(use)][:inst]::Expr, intermediaries, new_preserves) + for (useidx, new_preserves) in preserve_uses + ir[SSAValue(useidx)][:inst] = form_new_preserves(ir[SSAValue(useidx)][:inst]::Expr, + intermediaries, new_preserves) end @label skip diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 03feb881eec53..998a589c6905b 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -356,7 +356,8 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta end function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodInstance, - valid_worlds::WorldRange, @nospecialize(inferred_result)) + valid_worlds::WorldRange, @nospecialize(inferred_result), + ipo_effects::Effects) # If we decided not to optimize, drop the OptimizationState now. # External interpreters can override as necessary to cache additional information if inferred_result isa OptimizationState @@ -391,7 +392,7 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred - inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src) + inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src, result.ipo_effects) code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) if track_newly_inferred[] m = linfo.def diff --git a/base/compiler/types.jl b/base/compiler/types.jl index eff601dd7dc6e..0d8275ac0e7a0 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -59,6 +59,7 @@ function Effects( end const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, false) +const EFFECTS_THROWN = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE, false) const EFFECTS_UNKNOWN = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, true) function Effects(e::Effects = EFFECTS_UNKNOWN; diff --git a/base/condition.jl b/base/condition.jl index c536eceec17a0..4965b43a7019b 100644 --- a/base/condition.jl +++ b/base/condition.jl @@ -61,12 +61,12 @@ Abstract implementation of a condition object for synchronizing tasks objects with a given lock. """ struct GenericCondition{L<:AbstractLock} - waitq::InvasiveLinkedList{Task} + waitq::IntrusiveLinkedList{Task} lock::L - GenericCondition{L}() where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), L()) - GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), l) - GenericCondition(l::AbstractLock) = new{typeof(l)}(InvasiveLinkedList{Task}(), l) + GenericCondition{L}() where {L<:AbstractLock} = new{L}(IntrusiveLinkedList{Task}(), L()) + GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}(IntrusiveLinkedList{Task}(), l) + GenericCondition(l::AbstractLock) = new{typeof(l)}(IntrusiveLinkedList{Task}(), l) end assert_havelock(c::GenericCondition) = assert_havelock(c.lock) diff --git a/base/linked_list.jl b/base/linked_list.jl index 113607f63a2ff..c477dc56bdb2b 100644 --- a/base/linked_list.jl +++ b/base/linked_list.jl @@ -1,23 +1,23 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -mutable struct InvasiveLinkedList{T} +mutable struct IntrusiveLinkedList{T} # Invasive list requires that T have a field `.next >: U{T, Nothing}` and `.queue >: U{ILL{T}, Nothing}` head::Union{T, Nothing} tail::Union{T, Nothing} - InvasiveLinkedList{T}() where {T} = new{T}(nothing, nothing) + IntrusiveLinkedList{T}() where {T} = new{T}(nothing, nothing) end #const list_append!! = append! #const list_deletefirst! = delete! -eltype(::Type{<:InvasiveLinkedList{T}}) where {T} = @isdefined(T) ? T : Any +eltype(::Type{<:IntrusiveLinkedList{T}}) where {T} = @isdefined(T) ? T : Any -iterate(q::InvasiveLinkedList) = (h = q.head; h === nothing ? nothing : (h, h)) -iterate(q::InvasiveLinkedList{T}, v::T) where {T} = (h = v.next; h === nothing ? nothing : (h, h)) +iterate(q::IntrusiveLinkedList) = (h = q.head; h === nothing ? nothing : (h, h)) +iterate(q::IntrusiveLinkedList{T}, v::T) where {T} = (h = v.next; h === nothing ? nothing : (h, h)) -isempty(q::InvasiveLinkedList) = (q.head === nothing) +isempty(q::IntrusiveLinkedList) = (q.head === nothing) -function length(q::InvasiveLinkedList) +function length(q::IntrusiveLinkedList) i = 0 head = q.head while head !== nothing @@ -27,7 +27,7 @@ function length(q::InvasiveLinkedList) return i end -function list_append!!(q::InvasiveLinkedList{T}, q2::InvasiveLinkedList{T}) where T +function list_append!!(q::IntrusiveLinkedList{T}, q2::IntrusiveLinkedList{T}) where T q === q2 && error("can't append list to itself") head2 = q2.head if head2 !== nothing @@ -49,7 +49,7 @@ function list_append!!(q::InvasiveLinkedList{T}, q2::InvasiveLinkedList{T}) wher return q end -function push!(q::InvasiveLinkedList{T}, val::T) where T +function push!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === nothing || error("val already in a list") val.queue = q tail = q.tail @@ -62,7 +62,7 @@ function push!(q::InvasiveLinkedList{T}, val::T) where T return q end -function pushfirst!(q::InvasiveLinkedList{T}, val::T) where T +function pushfirst!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === nothing || error("val already in a list") val.queue = q head = q.head @@ -75,20 +75,20 @@ function pushfirst!(q::InvasiveLinkedList{T}, val::T) where T return q end -function pop!(q::InvasiveLinkedList{T}) where {T} +function pop!(q::IntrusiveLinkedList{T}) where {T} val = q.tail::T list_deletefirst!(q, val) # expensive! return val end -function popfirst!(q::InvasiveLinkedList{T}) where {T} +function popfirst!(q::IntrusiveLinkedList{T}) where {T} val = q.head::T list_deletefirst!(q, val) # cheap return val end # this function assumes `val` is found in `q` -function list_deletefirst!(q::InvasiveLinkedList{T}, val::T) where T +function list_deletefirst!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === q || return head = q.head::T if head === val @@ -125,20 +125,20 @@ end mutable struct LinkedListItem{T} # Adapter class to use any `T` in a LinkedList next::Union{LinkedListItem{T}, Nothing} - queue::Union{InvasiveLinkedList{LinkedListItem{T}}, Nothing} + queue::Union{IntrusiveLinkedList{LinkedListItem{T}}, Nothing} value::T LinkedListItem{T}(value::T) where {T} = new{T}(nothing, nothing, value) end -const LinkedList{T} = InvasiveLinkedList{LinkedListItem{T}} +const LinkedList{T} = IntrusiveLinkedList{LinkedListItem{T}} # delegate methods, as needed eltype(::Type{<:LinkedList{T}}) where {T} = @isdefined(T) ? T : Any iterate(q::LinkedList) = (h = q.head; h === nothing ? nothing : (h.value, h)) -iterate(q::InvasiveLinkedList{LLT}, v::LLT) where {LLT<:LinkedListItem} = (h = v.next; h === nothing ? nothing : (h.value, h)) +iterate(q::IntrusiveLinkedList{LLT}, v::LLT) where {LLT<:LinkedListItem} = (h = v.next; h === nothing ? nothing : (h.value, h)) push!(q::LinkedList{T}, val::T) where {T} = push!(q, LinkedListItem{T}(val)) pushfirst!(q::LinkedList{T}, val::T) where {T} = pushfirst!(q, LinkedListItem{T}(val)) -pop!(q::LinkedList) = invoke(pop!, Tuple{InvasiveLinkedList,}, q).value -popfirst!(q::LinkedList) = invoke(popfirst!, Tuple{InvasiveLinkedList,}, q).value +pop!(q::LinkedList) = invoke(pop!, Tuple{IntrusiveLinkedList,}, q).value +popfirst!(q::LinkedList) = invoke(popfirst!, Tuple{IntrusiveLinkedList,}, q).value function list_deletefirst!(q::LinkedList{T}, val::T) where T h = q.head while h !== nothing diff --git a/base/partr.jl b/base/partr.jl new file mode 100644 index 0000000000000..debf38fb72930 --- /dev/null +++ b/base/partr.jl @@ -0,0 +1,181 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Partr + +using ..Threads: SpinLock, nthreads + +# a task minheap +mutable struct taskheap + const lock::SpinLock + const tasks::Vector{Task} + @atomic ntasks::Int32 + @atomic priority::UInt16 + taskheap() = new(SpinLock(), Vector{Task}(undef, 256), zero(Int32), typemax(UInt16)) +end + + +# multiqueue minheap state +const heap_d = UInt32(8) +global heaps::Vector{taskheap} = Vector{taskheap}(undef, 0) +const heaps_lock = SpinLock() +global cong_unbias::UInt32 = typemax(UInt32) + + +cong(max::UInt32, unbias::UInt32) = ccall(:jl_rand_ptls, UInt32, (UInt32, UInt32), max, unbias) + UInt32(1) + +function unbias_cong(max::UInt32) + return typemax(UInt32) - ((typemax(UInt32) % max) + UInt32(1)) +end + + +function multiq_sift_up(heap::taskheap, idx::Int32) + while idx > Int32(1) + parent = (idx - Int32(2)) ÷ heap_d + Int32(1) + if heap.tasks[idx].priority < heap.tasks[parent].priority + t = heap.tasks[parent] + heap.tasks[parent] = heap.tasks[idx] + heap.tasks[idx] = t + idx = parent + else + break + end + end +end + + +function multiq_sift_down(heap::taskheap, idx::Int32) + if idx <= heap.ntasks + for child = (heap_d * idx - heap_d + Int32(2)):(heap_d * idx + Int32(1)) + child > length(heap.tasks) && break + if isassigned(heap.tasks, Int(child)) && + heap.tasks[child].priority < heap.tasks[idx].priority + t = heap.tasks[idx] + heap.tasks[idx] = heap.tasks[child] + heap.tasks[child] = t + multiq_sift_down(heap, child) + end + end + end +end + + +function multiq_size() + heap_c = UInt32(2) + heap_p = UInt32(length(heaps)) + nt = UInt32(nthreads()) + + if heap_c * nt <= heap_p + return heap_p + end + + @lock heaps_lock begin + heap_p = UInt32(length(heaps)) + nt = UInt32(nthreads()) + if heap_c * nt <= heap_p + return heap_p + end + + heap_p += heap_c * nt + newheaps = Vector{taskheap}(undef, heap_p) + copyto!(newheaps, heaps) + for i = (1 + length(heaps)):heap_p + newheaps[i] = taskheap() + end + global heaps = newheaps + global cong_unbias = unbias_cong(heap_p) + end + + return heap_p +end + + +function multiq_insert(task::Task, priority::UInt16) + task.priority = priority + + heap_p = multiq_size() + rn = cong(heap_p, cong_unbias) + while !trylock(heaps[rn].lock) + rn = cong(heap_p, cong_unbias) + end + + heap = heaps[rn] + if heap.ntasks >= length(heap.tasks) + resize!(heap.tasks, length(heap.tasks) * 2) + end + + ntasks = heap.ntasks + Int32(1) + @atomic :monotonic heap.ntasks = ntasks + heap.tasks[ntasks] = task + multiq_sift_up(heap, ntasks) + priority = heap.priority + if task.priority < priority + @atomic :monotonic heap.priority = task.priority + end + unlock(heap.lock) + + return true +end + + +function multiq_deletemin() + local rn1, rn2 + local prio1, prio2 + + @label retry + GC.safepoint() + heap_p = UInt32(length(heaps)) + for i = UInt32(0):heap_p + if i == heap_p + return nothing + end + rn1 = cong(heap_p, cong_unbias) + rn2 = cong(heap_p, cong_unbias) + prio1 = heaps[rn1].priority + prio2 = heaps[rn2].priority + if prio1 > prio2 + prio1 = prio2 + rn1 = rn2 + elseif prio1 == prio2 && prio1 == typemax(UInt16) + continue + end + if trylock(heaps[rn1].lock) + if prio1 == heaps[rn1].priority + break + end + unlock(heaps[rn1].lock) + end + end + + heap = heaps[rn1] + task = heap.tasks[1] + tid = Threads.threadid() + if ccall(:jl_set_task_tid, Cint, (Any, Cint), task, tid-1) == 0 + unlock(heap.lock) + @goto retry + end + ntasks = heap.ntasks + @atomic :monotonic heap.ntasks = ntasks - Int32(1) + heap.tasks[1] = heap.tasks[ntasks] + Base._unsetindex!(heap.tasks, Int(ntasks)) + prio1 = typemax(UInt16) + if ntasks > 1 + multiq_sift_down(heap, Int32(1)) + prio1 = heap.tasks[1].priority + end + @atomic :monotonic heap.priority = prio1 + unlock(heap.lock) + + return task +end + + +function multiq_check_empty() + for i = UInt32(1):length(heaps) + if heaps[i].ntasks != 0 + return false + end + end + return true +end + +end diff --git a/base/pcre.jl b/base/pcre.jl index 81e9b1d4d0ff8..d689e9be29113 100644 --- a/base/pcre.jl +++ b/base/pcre.jl @@ -32,7 +32,6 @@ _tid() = Int(ccall(:jl_threadid, Int16, ())) + 1 _nth() = Int(unsafe_load(cglobal(:jl_n_threads, Cint))) function get_local_match_context() - global THREAD_MATCH_CONTEXTS tid = _tid() ctxs = THREAD_MATCH_CONTEXTS if length(ctxs) < tid @@ -40,7 +39,10 @@ function get_local_match_context() l = PCRE_COMPILE_LOCK::Threads.SpinLock lock(l) try - THREAD_MATCH_CONTEXTS = ctxs = copyto!(fill(C_NULL, _nth()), THREAD_MATCH_CONTEXTS) + ctxs = THREAD_MATCH_CONTEXTS + if length(ctxs) < tid + global THREAD_MATCH_CONTEXTS = ctxs = copyto!(fill(C_NULL, _nth()), ctxs) + end finally unlock(l) end @@ -49,18 +51,7 @@ function get_local_match_context() if ctx == C_NULL # slow path to allocate it ctx = create_match_context() - l = PCRE_COMPILE_LOCK - if l === nothing - THREAD_MATCH_CONTEXTS[tid] = ctx - else - l = l::Threads.SpinLock - lock(l) - try - THREAD_MATCH_CONTEXTS[tid] = ctx - finally - unlock(l) - end - end + THREAD_MATCH_CONTEXTS[tid] = ctx end return ctx end diff --git a/base/strings/substring.jl b/base/strings/substring.jl index 069cf6af36c4b..b8a0de1948326 100644 --- a/base/strings/substring.jl +++ b/base/strings/substring.jl @@ -7,6 +7,9 @@ Like [`getindex`](@ref), but returns a view into the parent string `s` within range `i:j` or `r` respectively instead of making a copy. +The [`@views`](@ref) macro converts any string slices `s[i:j]` into +substrings `SubString(s, i, j)` in a block of code. + # Examples ```jldoctest julia> SubString("abc", 1, 2) diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index e687d94365c4a..902a27b942d4e 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -270,10 +270,64 @@ julia> textwidth("March") """ textwidth(s::AbstractString) = mapreduce(textwidth, +, s; init=0) +""" + lowercase(c::AbstractChar) + +Convert `c` to lowercase. + +See also [`uppercase`](@ref), [`titlecase`](@ref). + +# Examples +```jldoctest +julia> lowercase('A') +'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase) + +julia> lowercase('Ö') +'ö': Unicode U+00F6 (category Ll: Letter, lowercase) +``` +""" lowercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('A' <= c <= 'Z' ? c + 0x20 : c) : T(ccall(:utf8proc_tolower, UInt32, (UInt32,), c)) + +""" + uppercase(c::AbstractChar) + +Convert `c` to uppercase. + +See also [`lowercase`](@ref), [`titlecase`](@ref). + +# Examples +```jldoctest +julia> uppercase('a') +'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase) + +julia> uppercase('ê') +'Ê': Unicode U+00CA (category Lu: Letter, uppercase) +``` +""" uppercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_toupper, UInt32, (UInt32,), c)) + +""" + titlecase(c::AbstractChar) + +Convert `c` to titlecase. This may differ from uppercase for digraphs, +compare the example below. + +See also [`uppercase`](@ref), [`lowercase`](@ref). + +# Examples +```jldoctest +julia> titlecase('a') +'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase) + +julia> titlecase('dž') +'Dž': Unicode U+01C5 (category Lt: Letter, titlecase) + +julia> uppercase('dž') +'DŽ': Unicode U+01C4 (category Lu: Letter, uppercase) +``` +""" titlecase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_totitle, UInt32, (UInt32,), c)) diff --git a/base/task.jl b/base/task.jl index fe091fd4c7ad3..6d5ce6c39eb20 100644 --- a/base/task.jl +++ b/base/task.jl @@ -479,6 +479,12 @@ isolating the asynchronous code from changes to the variable's value in the curr Interpolating values via `\$` is available as of Julia 1.4. """ macro async(expr) + do_async_macro(expr) +end + +# generate the code for @async, possibly wrapping the task in something before +# pushing it to the wait queue. +function do_async_macro(expr; wrap=identity) letargs = Base._lift_one_interp!(expr) thunk = esc(:(()->($expr))) @@ -487,7 +493,7 @@ macro async(expr) let $(letargs...) local task = Task($thunk) if $(Expr(:islocal, var)) - put!($var, task) + put!($var, $(wrap(:task))) end schedule(task) task @@ -495,6 +501,35 @@ macro async(expr) end end +# task wrapper that doesn't create exceptions wrapped in TaskFailedException +struct UnwrapTaskFailedException + task::Task +end + +# common code for wait&fetch for UnwrapTaskFailedException +function unwrap_task_failed(f::Function, t::UnwrapTaskFailedException) + try + f(t.task) + catch ex + if ex isa TaskFailedException + throw(ex.task.exception) + else + rethrow() + end + end +end + +# the unwrapping for above task wrapper (gets triggered in sync_end()) +wait(t::UnwrapTaskFailedException) = unwrap_task_failed(wait, t) + +# same for fetching the tasks, for convenience +fetch(t::UnwrapTaskFailedException) = unwrap_task_failed(fetch, t) + +# macro for running async code that doesn't throw wrapped exceptions +macro async_unwrap(expr) + do_async_macro(expr, wrap=task->:(Base.UnwrapTaskFailedException($task))) +end + """ errormonitor(t::Task) @@ -621,14 +656,14 @@ end ## scheduler and work queue -struct InvasiveLinkedListSynchronized{T} - queue::InvasiveLinkedList{T} +struct IntrusiveLinkedListSynchronized{T} + queue::IntrusiveLinkedList{T} lock::Threads.SpinLock - InvasiveLinkedListSynchronized{T}() where {T} = new(InvasiveLinkedList{T}(), Threads.SpinLock()) + IntrusiveLinkedListSynchronized{T}() where {T} = new(IntrusiveLinkedList{T}(), Threads.SpinLock()) end -isempty(W::InvasiveLinkedListSynchronized) = isempty(W.queue) -length(W::InvasiveLinkedListSynchronized) = length(W.queue) -function push!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +isempty(W::IntrusiveLinkedListSynchronized) = isempty(W.queue) +length(W::IntrusiveLinkedListSynchronized) = length(W.queue) +function push!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try push!(W.queue, t) @@ -637,7 +672,7 @@ function push!(W::InvasiveLinkedListSynchronized{T}, t::T) where T end return W end -function pushfirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +function pushfirst!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try pushfirst!(W.queue, t) @@ -646,7 +681,7 @@ function pushfirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T end return W end -function pop!(W::InvasiveLinkedListSynchronized) +function pop!(W::IntrusiveLinkedListSynchronized) lock(W.lock) try return pop!(W.queue) @@ -654,7 +689,7 @@ function pop!(W::InvasiveLinkedListSynchronized) unlock(W.lock) end end -function popfirst!(W::InvasiveLinkedListSynchronized) +function popfirst!(W::IntrusiveLinkedListSynchronized) lock(W.lock) try return popfirst!(W.queue) @@ -662,7 +697,7 @@ function popfirst!(W::InvasiveLinkedListSynchronized) unlock(W.lock) end end -function list_deletefirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +function list_deletefirst!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try list_deletefirst!(W.queue, t) @@ -672,28 +707,36 @@ function list_deletefirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T return W end -const StickyWorkqueue = InvasiveLinkedListSynchronized{Task} -global const Workqueues = [StickyWorkqueue()] -global const Workqueue = Workqueues[1] # default work queue is thread 1 -function __preinit_threads__() - if length(Workqueues) < Threads.nthreads() - resize!(Workqueues, Threads.nthreads()) - for i = 2:length(Workqueues) - Workqueues[i] = StickyWorkqueue() +const StickyWorkqueue = IntrusiveLinkedListSynchronized{Task} +global Workqueues::Vector{StickyWorkqueue} = [StickyWorkqueue()] +const Workqueues_lock = Threads.SpinLock() +const Workqueue = Workqueues[1] # default work queue is thread 1 // TODO: deprecate this variable + +function workqueue_for(tid::Int) + qs = Workqueues + if length(qs) >= tid && isassigned(qs, tid) + return @inbounds qs[tid] + end + # slow path to allocate it + l = Workqueues_lock + @lock l begin + qs = Workqueues + if length(qs) < tid + nt = Threads.nthreads() + @assert tid <= nt + global Workqueues = qs = copyto!(typeof(qs)(undef, length(qs) + nt - 1), qs) end + if !isassigned(qs, tid) + @inbounds qs[tid] = StickyWorkqueue() + end + return @inbounds qs[tid] end - nothing end function enq_work(t::Task) (t._state === task_state_runnable && t.queue === nothing) || error("schedule: Task not runnable") - tid = Threads.threadid(t) - # Note there are three reasons a Task might be put into a sticky queue - # even if t.sticky == false: - # 1. The Task's stack is currently being used by the scheduler for a certain thread. - # 2. There is only 1 thread. - # 3. The multiq is full (can be fixed by making it growable). if t.sticky || Threads.nthreads() == 1 + tid = Threads.threadid(t) if tid == 0 # Issue #41324 # t.sticky && tid == 0 is a task that needs to be co-scheduled with @@ -704,18 +747,10 @@ function enq_work(t::Task) tid = Threads.threadid() ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid-1) end - push!(Workqueues[tid], t) + push!(workqueue_for(tid), t) else - if ccall(:jl_enqueue_task, Cint, (Any,), t) != 0 - # if multiq is full, give to a random thread (TODO fix) - if tid == 0 - tid = mod(time_ns() % Int, Threads.nthreads()) + 1 - ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid-1) - end - push!(Workqueues[tid], t) - else - tid = 0 - end + Partr.multiq_insert(t, t.priority) + tid = 0 end ccall(:jl_wakeup_thread, Cvoid, (Int16,), (tid - 1) % Int16) return t @@ -856,12 +891,12 @@ end function ensure_rescheduled(othertask::Task) ct = current_task() - W = Workqueues[Threads.threadid()] + W = workqueue_for(Threads.threadid()) if ct !== othertask && othertask._state === task_state_runnable # we failed to yield to othertask # return it to the head of a queue to be retried later tid = Threads.threadid(othertask) - Wother = tid == 0 ? W : Workqueues[tid] + Wother = tid == 0 ? W : workqueue_for(tid) pushfirst!(Wother, othertask) end # if the current task was queued, @@ -872,24 +907,28 @@ function ensure_rescheduled(othertask::Task) end function trypoptask(W::StickyWorkqueue) - isempty(W) && return - t = popfirst!(W) - if t._state !== task_state_runnable - # assume this somehow got queued twice, - # probably broken now, but try discarding this switch and keep going - # can't throw here, because it's probably not the fault of the caller to wait - # and don't want to use print() here, because that may try to incur a task switch - ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), - "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :runnable\n") - return + while !isempty(W) + t = popfirst!(W) + if t._state !== task_state_runnable + # assume this somehow got queued twice, + # probably broken now, but try discarding this switch and keep going + # can't throw here, because it's probably not the fault of the caller to wait + # and don't want to use print() here, because that may try to incur a task switch + ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), + "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :runnable\n") + continue + end + return t end - return t + return Partr.multiq_deletemin() end +checktaskempty = Partr.multiq_check_empty + @noinline function poptask(W::StickyWorkqueue) task = trypoptask(W) if !(task isa Task) - task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any), trypoptask, W) + task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any, Any), trypoptask, W, checktaskempty) end set_next_task(task) nothing @@ -897,7 +936,7 @@ end function wait() GC.safepoint() - W = Workqueues[Threads.threadid()] + W = workqueue_for(Threads.threadid()) poptask(W) result = try_yieldto(ensure_rescheduled) process_events() diff --git a/base/views.jl b/base/views.jl index e26359a5c9fd7..8553695868d6c 100644 --- a/base/views.jl +++ b/base/views.jl @@ -214,6 +214,8 @@ to return a view. Scalar indices, non-array types, and explicit [`getindex`](@ref) calls (as opposed to `array[...]`) are unaffected. +Similarly, `@views` converts string slices into [`SubString`](@ref) views. + !!! note The `@views` macro only affects `array[...]` expressions that appear explicitly in the given `expression`, not array slicing that diff --git a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 b/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 deleted file mode 100644 index e9e9e90dc3db5..0000000000000 --- a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -cbf1ec373e14a1417e40bc6c672ff5ff diff --git a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 b/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 deleted file mode 100644 index 85e24b205834c..0000000000000 --- a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -c14e843cfe11e4073f244c703573d6a3b9a8d3c8da0d6e0d23b3d63925c9d401c6c7f012ee96f010fa75eafa8a77efb714477b767d56dad50fbc71f8888afc8d diff --git a/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/md5 b/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/md5 new file mode 100644 index 0000000000000..08aa093eef201 --- /dev/null +++ b/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/md5 @@ -0,0 +1 @@ +382d186d7908db5e017e4120ddf67116 diff --git a/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/sha512 b/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/sha512 new file mode 100644 index 0000000000000..07882d8f62ec2 --- /dev/null +++ b/deps/checksums/Downloads-a7a034668e85f45169568cc0ee47aa43ab7dbd67.tar.gz/sha512 @@ -0,0 +1 @@ +b3e4b4911aaf94818818ef6ae7d18f0388a12b3ba7bddbc8e1bb5ce38cebc4c04e2e7306e5c59d808a84038b7cd4ab12eca4b3c6280cd13f7e74615ddb0f432f diff --git a/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/md5 b/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/md5 new file mode 100644 index 0000000000000..d247208595159 --- /dev/null +++ b/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/md5 @@ -0,0 +1 @@ +a6f48b4fbfecc10d6340536957d094a0 diff --git a/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/sha512 b/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/sha512 new file mode 100644 index 0000000000000..8699e2cb530aa --- /dev/null +++ b/deps/checksums/SparseArrays-96820d3aba22dad0fbd2b4877e6a1f0f7af76721.tar.gz/sha512 @@ -0,0 +1 @@ +5d74fada2c2748606683f5ffa457d185790ec68a6019717bf587e302b4a42ea6a18041bd786be8f0938a7919d486039cabcc4fbb736bcb4ef9d1aaf9eb697856 diff --git a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 b/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 deleted file mode 100644 index dc29e4d73a572..0000000000000 --- a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -c800768d427797e16c1dfb050e3615f6 diff --git a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 b/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 deleted file mode 100644 index 093da363d2c6a..0000000000000 --- a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -fe7966ba74a473c99c591bcc18ac8827949b8a764bf4e70991567cb0eb7fc8adeb2674ee09467ef431684d4ad3dbfc81d5f3df0bea7c48ca3a212b8de446303a diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index 22c2f66f9b8b0..a87bf5fa0aaa7 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -371,7 +371,8 @@ As an alternative for very simple cases, it is possible to just create a global per pointer using ```c -jl_set_global(jl_main_module, jl_symbol("var"), var); +jl_binding_t *bp = jl_get_binding_wr(jl_main_module, jl_symbol("var"), 1); +jl_checked_assignment(bp, val); ``` ### Updating fields of GC-managed objects diff --git a/doc/src/manual/interfaces.md b/doc/src/manual/interfaces.md index 30ee82e3040b0..8c29bd70ca141 100644 --- a/doc/src/manual/interfaces.md +++ b/doc/src/manual/interfaces.md @@ -258,7 +258,7 @@ provides a traits-based mechanism to enable efficient generic code for all array This distinction determines which scalar indexing methods the type must define. `IndexLinear()` arrays are simple: just define `getindex(A::ArrayType, i::Int)`. When the array is subsequently -indexed with a multidimensional set of indices, the fallback `getindex(A::AbstractArray, I...)()` +indexed with a multidimensional set of indices, the fallback `getindex(A::AbstractArray, I...)` efficiently converts the indices into one linear index and then calls the above method. `IndexCartesian()` arrays, on the other hand, require methods to be defined for each supported dimensionality with `ndims(A)` `Int` indices. For example, [`SparseMatrixCSC`](@ref) from the `SparseArrays` standard diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index 0d5f30e5c7631..cc6c7f897353e 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -364,7 +364,7 @@ There are a few approaches to dealing with this problem: 3. A related third strategy is to use a yield-free queue. We don't currently have a lock-free queue implemented in Base, but - `Base.InvasiveLinkedListSynchronized{T}` is suitable. This can frequently be a + `Base.IntrusiveLinkedListSynchronized{T}` is suitable. This can frequently be a good strategy to use for code with event loops. For example, this strategy is employed by `Gtk.jl` to manage lifetime ref-counting. In this approach, we don't do any explicit work inside the `finalizer`, and instead add it to a queue diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 3dd09b207ddda..8403b71b524a4 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -13,7 +13,7 @@ The functions should take arguments, instead of operating directly on global var ## Avoid untyped global variables -An untyped global variable might have its value, and therefore possibly its type, changed at any point. This makes +The value of an untyped global variable might change at any point, possibly leading to a change of its type. This makes it difficult for the compiler to optimize code using global variables. This also applies to type-valued variables, i.e. type aliases on the global level. Variables should be local, or passed as arguments to functions, whenever possible. diff --git a/doc/src/manual/strings.md b/doc/src/manual/strings.md index ee2b3f9d71d54..be3f76bb99683 100644 --- a/doc/src/manual/strings.md +++ b/doc/src/manual/strings.md @@ -242,8 +242,9 @@ The former is a single character value of type `Char`, while the latter is a str happens to contain only a single character. In Julia these are very different things. Range indexing makes a copy of the selected part of the original string. -Alternatively, it is possible to create a view into a string using the type [`SubString`](@ref), -for example: +Alternatively, it is possible to create a view into a string using the type [`SubString`](@ref). +More simply, using the [`@views`](@ref) macro on a block of code converts all string slices +into substrings. For example: ```jldoctest julia> str = "long string" @@ -254,6 +255,9 @@ julia> substr = SubString(str, 1, 4) julia> typeof(substr) SubString{String} + +julia> @views typeof(str[1:4]) # @views converts slices to SubStrings +SubString{String} ``` Several standard functions like [`chop`](@ref), [`chomp`](@ref) or [`strip`](@ref) diff --git a/src/ast.scm b/src/ast.scm index 35e0632bea3c8..1e7f5b0ef1b72 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -79,13 +79,15 @@ ((char? e) (string "'" e "'")) ((atom? e) (string e)) ((eq? (car e) '|.|) - (string (deparse (cadr e)) '|.| - (cond ((and (pair? (caddr e)) (memq (caaddr e) '(quote inert))) - (deparse-colon-dot (cadr (caddr e)))) - ((and (pair? (caddr e)) (eq? (caaddr e) 'copyast)) - (deparse-colon-dot (cadr (cadr (caddr e))))) - (else - (string #\( (deparse (caddr e)) #\)))))) + (if (length= e 2) + (string "(." (deparse (cadr e)) ")") + (string (deparse (cadr e)) '|.| + (cond ((and (pair? (caddr e)) (memq (caaddr e) '(quote inert))) + (deparse-colon-dot (cadr (caddr e)))) + ((and (pair? (caddr e)) (eq? (caaddr e) 'copyast)) + (deparse-colon-dot (cadr (cadr (caddr e))))) + (else + (string #\( (deparse (caddr e)) #\))))))) ((memq (car e) '(... |'|)) (string (deparse (cadr e)) (car e))) ((or (syntactic-op? (car e)) (eq? (car e) '|<:|) (eq? (car e) '|>:|) (eq? (car e) '-->)) @@ -445,7 +447,7 @@ (if (dotop-named? e) (error (string "invalid function name \"" (deparse e) "\"")) (if (pair? e) - (if (eq? (car e) '|.|) + (if (and (eq? (car e) '|.|) (length= e 3)) (check-dotop (caddr e)) (if (quoted? e) (check-dotop (cadr e)))))) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 46adef8444aa9..c820751ab56e2 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -56,6 +56,7 @@ DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(_typevar); DECLARE_BUILTIN(donotdelete); DECLARE_BUILTIN(getglobal); +DECLARE_BUILTIN(setglobal); JL_CALLABLE(jl_f_invoke_kwsorter); #ifdef DEFINE_BUILTIN_GLOBALS diff --git a/src/builtins.c b/src/builtins.c index f81069424d784..30840f4edaf5c 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1203,7 +1203,7 @@ JL_CALLABLE(jl_f_setglobal) if (order == jl_memory_order_notatomic) jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); // is seq_cst already, no fence needed - jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); + jl_binding_t *b = jl_get_binding_wr_or_error((jl_module_t*)args[0], (jl_sym_t*)args[1]); jl_checked_assignment(b, args[2]); return args[2]; } @@ -1218,7 +1218,7 @@ JL_CALLABLE(jl_f_get_binding_type) jl_value_t *ty = jl_binding_type(mod, sym); if (ty == (jl_value_t*)jl_nothing) { jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); - if (b) { + if (b && b->owner == mod) { jl_value_t *old_ty = NULL; jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); return jl_atomic_load_relaxed(&b->ty); @@ -1920,7 +1920,7 @@ void jl_init_primitives(void) JL_GC_DISABLED // module bindings jl_builtin_getglobal = add_builtin_func("getglobal", jl_f_getglobal); - add_builtin_func("setglobal!", jl_f_setglobal); + jl_builtin_setglobal = add_builtin_func("setglobal!", jl_f_setglobal); add_builtin_func("get_binding_type", jl_f_get_binding_type); add_builtin_func("set_binding_type!", jl_f_set_binding_type); @@ -2021,10 +2021,11 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("Bool", (jl_value_t*)jl_bool_type); add_builtin("UInt8", (jl_value_t*)jl_uint8_type); - add_builtin("Int32", (jl_value_t*)jl_int32_type); - add_builtin("Int64", (jl_value_t*)jl_int64_type); + add_builtin("UInt16", (jl_value_t*)jl_uint16_type); add_builtin("UInt32", (jl_value_t*)jl_uint32_type); add_builtin("UInt64", (jl_value_t*)jl_uint64_type); + add_builtin("Int32", (jl_value_t*)jl_int32_type); + add_builtin("Int64", (jl_value_t*)jl_int64_type); #ifdef _P64 add_builtin("Int", (jl_value_t*)jl_int64_type); #else diff --git a/src/ccall.cpp b/src/ccall.cpp index a7e8b0f4daa7c..bbcc1a3ba075f 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -1927,14 +1927,37 @@ jl_cgval_t function_sig_t::emit_a_ccall( } else { assert(symarg.f_name != NULL); - const char* f_name = symarg.f_name; - bool f_extern = (strncmp(f_name, "extern ", 7) == 0); - if (f_extern) - f_name += 7; - llvmf = jl_Module->getOrInsertFunction(f_name, functype).getCallee(); - if (!f_extern && (!isa(llvmf) || - cast(llvmf)->getIntrinsicID() == - Intrinsic::not_intrinsic)) { + StringRef f_name(symarg.f_name); + bool f_extern = f_name.consume_front("extern "); + llvmf = NULL; + if (f_extern) { + llvmf = jl_Module->getOrInsertFunction(f_name, functype).getCallee(); + if (!isa(llvmf) || cast(llvmf)->isIntrinsic() || cast(llvmf)->getFunctionType() != functype) + llvmf = NULL; + } + else if (f_name.startswith("llvm.")) { + // compute and verify auto-mangling for intrinsic name + auto ID = Function::lookupIntrinsicID(f_name); + if (ID != Intrinsic::not_intrinsic) { + // Accumulate an array of overloaded types for the given intrinsic + // and compute the new name mangling schema + SmallVector overloadTys; + SmallVector Table; + getIntrinsicInfoTableEntries(ID, Table); + ArrayRef TableRef = Table; + auto res = Intrinsic::matchIntrinsicSignature(functype, TableRef, overloadTys); + if (res == Intrinsic::MatchIntrinsicTypes_Match) { + bool matchvararg = !Intrinsic::matchIntrinsicVarArg(functype->isVarArg(), TableRef); + if (matchvararg) { + Function *intrinsic = Intrinsic::getDeclaration(jl_Module, ID, overloadTys); + assert(intrinsic->getFunctionType() == functype); + if (intrinsic->getName() == f_name || Intrinsic::getBaseName(ID) == f_name) + llvmf = intrinsic; + } + } + } + } + if (llvmf == NULL) { emit_error(ctx, "llvmcall only supports intrinsic calls"); return jl_cgval_t(ctx.builder.getContext()); } diff --git a/src/cgutils.cpp b/src/cgutils.cpp index cc095459113c0..b48f6df40a01a 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -474,25 +474,22 @@ static Value *maybe_bitcast(jl_codectx_t &ctx, Value *V, Type *to) { return V; } -static Value *julia_binding_gv(jl_codectx_t &ctx, Value *bv) +static Value *julia_binding_pvalue(jl_codectx_t &ctx, Value *bv) { + bv = emit_bitcast(ctx, bv, ctx.types().T_pprjlvalue); Value *offset = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_binding_t, value) / sizeof(size_t)); return ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, bv, offset); } static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) { - // emit a literal_pointer_val to the value field of a jl_binding_t + // emit a literal_pointer_val to a jl_binding_t // binding->value are prefixed with * - Value *bv; if (imaging_mode) - bv = emit_bitcast(ctx, - tbaa_decorate(ctx.tbaa().tbaa_const, - ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, julia_pgv(ctx, "*", b->name, b->owner, b), Align(sizeof(void*)))), - ctx.types().T_pprjlvalue); + return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, + julia_pgv(ctx, "*", b->name, b->owner, b), Align(sizeof(void*)))); else - bv = ConstantExpr::getBitCast(literal_static_pointer_val(b, ctx.types().T_pjlvalue), ctx.types().T_pprjlvalue); - return julia_binding_gv(ctx, bv); + return literal_static_pointer_val(b, ctx.types().T_pjlvalue); } // --- mapping between julia and llvm types --- diff --git a/src/codegen.cpp b/src/codegen.cpp index b7483d1e88504..e783a9df3f460 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -651,6 +651,15 @@ static const auto jlgetbindingorerror_func = new JuliaFunction{ }, nullptr, }; +static const auto jlgetbindingwrorerror_func = new JuliaFunction{ + XSTR(jl_get_binding_wr_or_error), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(T_pjlvalue, + {T_pjlvalue, T_pjlvalue}, false); + }, + nullptr, +}; static const auto jlboundp_func = new JuliaFunction{ XSTR(jl_boundp), [](LLVMContext &C) { @@ -2387,16 +2396,19 @@ static void jl_add_method_root(jl_codectx_t &ctx, jl_value_t *val) // --- generating function calls --- -static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name) +static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order) { jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false); + if (bp == NULL) + return jl_cgval_t(ctx.builder.getContext()); + bp = julia_binding_pvalue(ctx, bp); if (bnd && bnd->value != NULL) { if (bnd->constp) { return mark_julia_const(ctx, bnd->value); } LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - v->setOrdering(AtomicOrdering::Unordered); + v->setOrdering(order); tbaa_decorate(ctx.tbaa().tbaa_binding, v); return mark_julia_type(ctx, v, true, bnd->ty); } @@ -2404,6 +2416,20 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); } +static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, const jl_cgval_t &rval_info, AtomicOrdering Order) +{ + Value *rval = boxed(ctx, rval_info); + if (bnd && !bnd->constp && bnd->ty && jl_subtype(rval_info.typ, bnd->ty)) { + StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); + v->setOrdering(Order); + tbaa_decorate(ctx.tbaa().tbaa_binding, v); + emit_write_barrier_binding(ctx, bp, rval); + } + else { + ctx.builder.CreateCall(prepare_call(jlcheckassign_func), { bp, mark_callee_rooted(ctx, rval) }); + } +} + static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1, Value *nullcheck2) { @@ -2668,6 +2694,49 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva return emit_box_compare(ctx, arg1, arg2, nullcheck1, nullcheck2); } +static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, + const jl_cgval_t *argv, size_t nargs, const jl_cgval_t *modifyop) +{ + const jl_cgval_t &mod = argv[1]; + const jl_cgval_t &sym = argv[2]; + const jl_cgval_t &val = argv[3]; + enum jl_memory_order order = jl_memory_order_unspecified; + + if (nargs == 4) { + const jl_cgval_t &arg4 = argv[4]; + if (arg4.constant && jl_is_symbol(arg4.constant)) + order = jl_get_atomic_order((jl_sym_t*)arg4.constant, false, true); + else + return false; + } + else + order = jl_memory_order_monotonic; + + if (order == jl_memory_order_invalid || order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, order == jl_memory_order_invalid ? "invalid atomic ordering" : "setglobal!: module binding cannot be written non-atomically"); + *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + return true; + } + + if (sym.constant && jl_is_symbol(sym.constant)) { + jl_sym_t *name = (jl_sym_t*)sym.constant; + if (mod.constant && jl_is_module(mod.constant)) { + jl_binding_t *bnd = NULL; + Value *bp = global_binding_pointer(ctx, (jl_module_t*)mod.constant, name, &bnd, true); + if (bp) { + emit_globalset(ctx, bnd, bp, val, get_llvm_atomic_order(order)); + *ret = val; + } + else { + *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + } + return true; + } + } + + return false; +} + static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, const jl_cgval_t *argv, size_t nargs, const jl_cgval_t *modifyop) { @@ -2706,7 +2775,7 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, jl_datatype_t *uty = (jl_datatype_t*)jl_unwrap_unionall(obj.typ); if (jl_is_datatype(uty) && jl_struct_try_layout(uty)) { ssize_t idx = -1; - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { idx = jl_field_index(uty, (jl_sym_t*)fld.constant, 0); } else if (fld.constant && fld.typ == (jl_value_t*)jl_long_type) { @@ -3138,7 +3207,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else if (nargs == 3) { const jl_cgval_t &arg3 = argv[3]; - if (arg3.typ == (jl_value_t*)jl_symbol_type && arg3.constant) + if (arg3.constant && jl_is_symbol(arg3.constant)) order = jl_get_atomic_order((jl_sym_t*)arg3.constant, true, false); else if (arg3.constant == jl_false) boundscheck = jl_false; @@ -3155,10 +3224,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (jl_is_type_type((jl_value_t*)utt) && jl_is_concrete_type(jl_tparam0(utt))) utt = (jl_datatype_t*)jl_typeof(jl_tparam0(utt)); - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { jl_sym_t *name = (jl_sym_t*)fld.constant; if (obj.constant && jl_is_module(obj.constant)) { - *ret = emit_globalref(ctx, (jl_module_t*)obj.constant, name); + *ret = emit_globalref(ctx, (jl_module_t*)obj.constant, name, order == jl_memory_order_unspecified ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); return true; } @@ -3257,7 +3326,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (nargs == 3) { const jl_cgval_t &arg3 = argv[3]; - if (arg3.typ == (jl_value_t*)jl_symbol_type && arg3.constant) + if (arg3.constant && jl_is_symbol(arg3.constant)) order = jl_get_atomic_order((jl_sym_t*)arg3.constant, true, false); else return false; @@ -3265,16 +3334,16 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else order = jl_memory_order_monotonic; - if (order == jl_memory_order_invalid) { - emit_atomic_error(ctx, "invalid atomic ordering"); + if (order == jl_memory_order_invalid || order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, order == jl_memory_order_invalid ? "invalid atomic ordering" : "getglobal: module binding cannot be read non-atomically"); *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable return true; } - if (sym.constant && sym.typ == (jl_value_t*)jl_symbol_type) { + if (sym.constant && jl_is_symbol(sym.constant)) { jl_sym_t *name = (jl_sym_t*)sym.constant; if (mod.constant && jl_is_module(mod.constant)) { - *ret = emit_globalref(ctx, (jl_module_t*)mod.constant, name); + *ret = emit_globalref(ctx, (jl_module_t*)mod.constant, name, get_llvm_atomic_order(order)); return true; } } @@ -3282,6 +3351,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } + else if (f == jl_builtin_setglobal && (nargs == 3 || nargs == 4)) { + return emit_f_opglobal(ctx, ret, f, argv, nargs, nullptr); + } + else if ((f == jl_builtin_setfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_swapfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_replacefield && (nargs == 4 || nargs == 5 || nargs == 6)) || @@ -3425,7 +3498,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, assert(jl_is_datatype(stt)); ssize_t fieldidx = -1; - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { jl_sym_t *sym = (jl_sym_t*)fld.constant; fieldidx = jl_field_index(stt, sym, 0); } @@ -3901,49 +3974,50 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t jl_binding_t **pbnd, bool assign) { jl_binding_t *b = NULL; - if (assign) { + if (assign) b = jl_get_binding_wr(m, s, 0); - assert(b != NULL); + else + b = jl_get_binding(m, s); + if (b == NULL) { + // var not found. switch to delayed lookup. + Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); + GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, + false, GlobalVariable::PrivateLinkage, initnul); + LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); + cachedval->setOrdering(AtomicOrdering::Unordered); + BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); + BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); + BasicBlock *currentbb = ctx.builder.GetInsertBlock(); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpNE(cachedval, initnul), have_val, not_found); + ctx.f->getBasicBlockList().push_back(not_found); + ctx.builder.SetInsertPoint(not_found); + Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func), + { literal_pointer_val(ctx, (jl_value_t*)m), + literal_pointer_val(ctx, (jl_value_t*)s) }); + ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + ctx.builder.CreateBr(have_val); + ctx.f->getBasicBlockList().push_back(have_val); + ctx.builder.SetInsertPoint(have_val); + PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); + p->addIncoming(cachedval, currentbb); + p->addIncoming(bval, not_found); + return p; + } + if (assign) { if (b->owner != m) { char *msg; (void)asprintf(&msg, "cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(s), jl_symbol_name(m->name)); emit_error(ctx, msg); free(msg); + return NULL; } } else { - b = jl_get_binding(m, s); - if (b == NULL) { - // var not found. switch to delayed lookup. - Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); - GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, initnul); - LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); - cachedval->setOrdering(AtomicOrdering::Unordered); - BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); - BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); - BasicBlock *currentbb = ctx.builder.GetInsertBlock(); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpNE(cachedval, initnul), have_val, not_found); - ctx.f->getBasicBlockList().push_back(not_found); - ctx.builder.SetInsertPoint(not_found); - Value *bval = ctx.builder.CreateCall(prepare_call(jlgetbindingorerror_func), - { literal_pointer_val(ctx, (jl_value_t*)m), - literal_pointer_val(ctx, (jl_value_t*)s) }); - ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); - ctx.builder.CreateBr(have_val); - ctx.f->getBasicBlockList().push_back(have_val); - ctx.builder.SetInsertPoint(have_val); - PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); - p->addIncoming(cachedval, currentbb); - p->addIncoming(bval, not_found); - return julia_binding_gv(ctx, emit_bitcast(ctx, p, ctx.types().T_pprjlvalue)); - } if (b->deprecated) cg_bdw(ctx, b); } - if (pbnd) - *pbnd = b; + *pbnd = b; return julia_binding_gv(ctx, b); } @@ -3988,7 +4062,8 @@ static jl_cgval_t emit_global(jl_codectx_t &ctx, jl_sym_t *sym) { jl_binding_t *jbp = NULL; Value *bp = global_binding_pointer(ctx, ctx.module, sym, &jbp, false); - assert(bp != NULL); + if (bp == NULL) + return jl_cgval_t(ctx.builder.getContext()); if (jbp && jbp->value != NULL) { if (jbp->constp) return mark_julia_const(ctx, jbp->value); @@ -4067,6 +4142,7 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) if (bnd->value != NULL) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); + bp = julia_binding_pvalue(ctx, bp); LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); tbaa_decorate(ctx.tbaa().tbaa_binding, v); v->setOrdering(AtomicOrdering::Unordered); @@ -4409,50 +4485,31 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t } } -static void emit_binding_store(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, jl_value_t *r, ssize_t ssaval, AtomicOrdering Order) -{ - assert(bnd); - jl_cgval_t rval_info = emit_expr(ctx, r, ssaval); - Value *rval = boxed(ctx, rval_info); - if (!bnd->constp && bnd->ty && jl_subtype(rval_info.typ, bnd->ty)) { - StoreInst *v = ctx.builder.CreateAlignedStore(rval, bp, Align(sizeof(void*))); - v->setOrdering(Order); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); - emit_write_barrier_binding(ctx, literal_pointer_val(ctx, bnd), rval); - } - else { - ctx.builder.CreateCall(prepare_call(jlcheckassign_func), - { literal_pointer_val(ctx, bnd), - mark_callee_rooted(ctx, rval) }); - } -} - static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssize_t ssaval) { assert(!jl_is_ssavalue(l)); + jl_cgval_t rval_info = emit_expr(ctx, r, ssaval); - jl_sym_t *s = NULL; jl_binding_t *bnd = NULL; Value *bp = NULL; if (jl_is_symbol(l)) - s = (jl_sym_t*)l; + bp = global_binding_pointer(ctx, ctx.module, (jl_sym_t*)l, &bnd, true); // now bp != NULL or bnd != NULL else if (jl_is_globalref(l)) - bp = global_binding_pointer(ctx, jl_globalref_mod(l), jl_globalref_name(l), &bnd, true); // now bp != NULL + bp = global_binding_pointer(ctx, jl_globalref_mod(l), jl_globalref_name(l), &bnd, true); // now bp != NULL or bnd != NULL else assert(jl_is_slot(l)); - if (bp == NULL && s != NULL) - bp = global_binding_pointer(ctx, ctx.module, s, &bnd, true); - if (bp != NULL) { // it's a global - emit_binding_store(ctx, bnd, bp, r, ssaval, AtomicOrdering::Unordered); - // Global variable. Does not need debug info because the debugger knows about - // its memory location. + if (bp != NULL || bnd != NULL) { // it is a global + if (bp != NULL) { + emit_globalset(ctx, bnd, bp, rval_info, AtomicOrdering::Unordered); + // Global variable. Does not need debug info because the debugger knows about + // its memory location. + } return; } int sl = jl_slot_number(l) - 1; // it's a local variable jl_varinfo_t &vi = ctx.slots[sl]; - jl_cgval_t rval_info = emit_expr(ctx, r, ssaval); emit_varinfo_assign(ctx, vi, rval_info, l); } @@ -4686,7 +4743,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } } if (jl_is_globalref(expr)) { - return emit_globalref(ctx, jl_globalref_mod(expr), jl_globalref_name(expr)); + return emit_globalref(ctx, jl_globalref_mod(expr), jl_globalref_name(expr), AtomicOrdering::Unordered); } if (jl_is_linenode(expr)) { jl_error("LineNumberNode in value position"); @@ -4832,6 +4889,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) return ghostValue(ctx, jl_nothing_type); } bp = julia_binding_gv(ctx, bnd); + bp = julia_binding_pvalue(ctx, bp); bp_owner = literal_pointer_val(ctx, (jl_value_t*)mod); } else if (jl_is_slot(mn) || jl_is_argument(mn)) { @@ -4879,9 +4937,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } if (jl_is_symbol(sym)) { jl_binding_t *bnd = NULL; - (void)global_binding_pointer(ctx, mod, sym, &bnd, true); assert(bnd); - ctx.builder.CreateCall(prepare_call(jldeclareconst_func), - literal_pointer_val(ctx, bnd)); + Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true); + if (bp) + ctx.builder.CreateCall(prepare_call(jldeclareconst_func), bp); } } else if (head == jl_new_sym) { @@ -6346,7 +6404,8 @@ static std::pair, jl_llvm_functions_t> // step 1. unpack AST and allocate codegen context for this function jl_llvm_functions_t declarations; jl_codectx_t ctx(ctxt, params); - JL_GC_PUSH2(&ctx.code, &ctx.roots); + jl_datatype_t *vatyp = NULL; + JL_GC_PUSH3(&ctx.code, &ctx.roots, &vatyp); ctx.code = src->code; std::map labels; @@ -6451,7 +6510,7 @@ static std::pair, jl_llvm_functions_t> if (va && ctx.vaSlot != -1) { jl_varinfo_t &varinfo = ctx.slots[ctx.vaSlot]; varinfo.isArgument = true; - jl_datatype_t *vatyp = specsig ? compute_va_type(lam, nreq) : (jl_tuple_type); + vatyp = specsig ? compute_va_type(lam, nreq) : (jl_tuple_type); varinfo.value = mark_julia_type(ctx, (Value*)NULL, false, vatyp); } @@ -8092,6 +8151,7 @@ static void init_jit_functions(void) add_named_global(jlcheckassign_func, &jl_checked_assignment); add_named_global(jldeclareconst_func, &jl_declare_constant); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); + add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr_or_error); add_named_global(jlboundp_func, &jl_boundp); for (auto it : builtin_func_map) add_named_global(it.second, it.first); diff --git a/src/datatype.c b/src/datatype.c index a153a42214581..746ce75af0e3f 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -1788,9 +1788,9 @@ JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT return fval != NULL ? 1 : 0; } -JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) JL_NOTSAFEPOINT +JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) { - if (ty->layout == NULL || field > jl_datatype_nfields(ty) || field < 1) + if (!jl_struct_try_layout(ty) || field > jl_datatype_nfields(ty) || field < 1) jl_bounds_error_int((jl_value_t*)ty, field); return jl_field_offset(ty, field - 1); } diff --git a/src/dump.c b/src/dump.c index f32461e6f7916..ef8db36411549 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2535,8 +2535,8 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) jl_module_t *mod = (jl_module_t*)v; if (mod->parent == mod) // top level modules handled by loader break; - jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1); - jl_declare_constant(b); // this can throw + jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1); // this can throw + jl_declare_constant(b); // this can also throw if (b->value != NULL) { if (!jl_is_module(b->value)) { jl_errorf("Invalid redefinition of constant %s.", diff --git a/src/gc.c b/src/gc.c index e41a2ee04c255..d4af5f443764c 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2824,7 +2824,6 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception); } -void jl_gc_mark_enqueued_tasks(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp); extern jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; // mark the initial root set @@ -2833,9 +2832,6 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) // modules gc_mark_queue_obj(gc_cache, sp, jl_main_module); - // tasks - jl_gc_mark_enqueued_tasks(gc_cache, sp); - // invisible builtin values if (jl_an_empty_vec_any != NULL) gc_mark_queue_obj(gc_cache, sp, jl_an_empty_vec_any); diff --git a/src/init.c b/src/init.c index 98d5081c1daaf..c6ff7842e3c57 100644 --- a/src/init.c +++ b/src/init.c @@ -731,17 +731,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ post_boot_hooks(); } - if (jl_base_module != NULL) { - // Do initialization needed before starting child threads - jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("__preinit_threads__")); - if (f) { - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); - jl_apply(&f, 1); - ct->world_age = last_age; - } - } - else { + if (jl_base_module == NULL) { // nthreads > 1 requires code in Base jl_n_threads = 1; } @@ -780,7 +770,6 @@ static void post_boot_hooks(void) jl_char_type = (jl_datatype_t*)core("Char"); jl_int8_type = (jl_datatype_t*)core("Int8"); jl_int16_type = (jl_datatype_t*)core("Int16"); - jl_uint16_type = (jl_datatype_t*)core("UInt16"); jl_float16_type = (jl_datatype_t*)core("Float16"); jl_float32_type = (jl_datatype_t*)core("Float32"); jl_float64_type = (jl_datatype_t*)core("Float64"); @@ -792,10 +781,11 @@ static void post_boot_hooks(void) jl_bool_type->super = jl_integer_type; jl_uint8_type->super = jl_unsigned_type; - jl_int32_type->super = jl_signed_type; - jl_int64_type->super = jl_signed_type; + jl_uint16_type->super = jl_unsigned_type; jl_uint32_type->super = jl_unsigned_type; jl_uint64_type->super = jl_unsigned_type; + jl_int32_type->super = jl_signed_type; + jl_int64_type->super = jl_signed_type; jl_errorexception_type = (jl_datatype_t*)core("ErrorException"); jl_stackovf_exception = jl_new_struct_uninit((jl_datatype_t*)core("StackOverflowError")); diff --git a/src/interpreter.c b/src/interpreter.c index 26038b4cfef35..60bd4a6e1ce7e 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -483,7 +483,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, sym = (jl_sym_t*)lhs; } JL_GC_PUSH1(&rhs); - jl_binding_t *b = jl_get_binding_wr(modu, sym, 1); + jl_binding_t *b = jl_get_binding_wr_or_error(modu, sym); jl_checked_assignment(b, rhs); JL_GC_POP(); } diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 2aed69f47c30a..e2f8679ebbd57 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -103,6 +103,7 @@ XX(jl_copy_ast) \ XX(jl_copy_code_info) \ XX(jl_cpu_threads) \ + XX(jl_effective_threads) \ XX(jl_crc32c_sw) \ XX(jl_create_system_image) \ XX(jl_cstr_to_string) \ @@ -119,7 +120,6 @@ XX(jl_egal__bits) \ XX(jl_egal__special) \ XX(jl_eh_restore_state) \ - XX(jl_enqueue_task) \ XX(jl_enter_handler) \ XX(jl_enter_threaded_region) \ XX(jl_environ) \ @@ -207,6 +207,7 @@ XX(jl_get_binding) \ XX(jl_get_binding_for_method_def) \ XX(jl_get_binding_or_error) \ + XX(jl_get_binding_wr_or_error) \ XX(jl_get_binding_wr) \ XX(jl_get_cpu_name) \ XX(jl_get_current_task) \ @@ -214,7 +215,6 @@ XX(jl_get_excstack) \ XX(jl_get_fenv_consts) \ XX(jl_get_field) \ - XX(jl_get_field_offset) \ XX(jl_get_global) \ XX(jl_get_image_file) \ XX(jl_get_JIT) \ @@ -412,7 +412,6 @@ XX(jl_set_ARGS) \ XX(jl_set_const) \ XX(jl_set_errno) \ - XX(jl_set_global) \ XX(jl_set_istopmod) \ XX(jl_set_module_compile) \ XX(jl_set_module_infer) \ diff --git a/src/jltypes.c b/src/jltypes.c index 5e84b200af937..61d8238e0cd37 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -63,6 +63,12 @@ static int layout_uses_free_typevars(jl_value_t *v, jl_typeenv_t *env) return 0; if (dt->name == jl_namedtuple_typename) return layout_uses_free_typevars(jl_tparam0(dt), env) || layout_uses_free_typevars(jl_tparam1(dt), env); + if (dt->name == jl_tuple_typename) + // conservative, since we don't want to inline an abstract tuple, + // and we currently declare !has_fixed_layout for these, but that + // means we also won't be able to inline a tuple which is concrete + // except for the use of free type-vars + return 1; jl_svec_t *types = jl_get_fieldtypes(dt); size_t i, l = jl_svec_len(types); for (i = 0; i < l; i++) { @@ -227,8 +233,10 @@ int jl_has_fixed_layout(jl_datatype_t *dt) return 1; if (dt->name->abstract) return 0; - if (jl_is_tuple_type(dt) || jl_is_namedtuple_type(dt)) - return 0; // TODO: relax more? + if (dt->name == jl_namedtuple_typename) + return !layout_uses_free_typevars(jl_tparam0(dt), NULL) && !layout_uses_free_typevars(jl_tparam1(dt), NULL); + if (dt->name == jl_tuple_typename) + return 0; jl_svec_t *types = jl_get_fieldtypes(dt); size_t i, l = jl_svec_len(types); for (i = 0; i < l; i++) { @@ -2144,6 +2152,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_emptysvec, 64); jl_uint8_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt8"), core, jl_any_type, jl_emptysvec, 8); + jl_uint16_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt16"), core, + jl_any_type, jl_emptysvec, 16); jl_ssavalue_type = jl_new_datatype(jl_symbol("SSAValue"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "id"), @@ -2508,7 +2518,7 @@ void jl_init_types(void) JL_GC_DISABLED "inferred", //"edges", //"absolute_max", - "ipo_purity_bits", "purity_bits", + "ipo_purity_bits", "purity_bits", "argescapes", "isspecsig", "precompile", "invoke", "specptr", // function object decls "relocatability"), @@ -2602,7 +2612,7 @@ void jl_init_types(void) JL_GC_DISABLED NULL, jl_any_type, jl_emptysvec, - jl_perm_symsvec(14, + jl_perm_symsvec(15, "next", "queue", "storage", @@ -2616,8 +2626,9 @@ void jl_init_types(void) JL_GC_DISABLED "rngState3", "_state", "sticky", - "_isexception"), - jl_svec(14, + "_isexception", + "priority"), + jl_svec(15, jl_any_type, jl_any_type, jl_any_type, @@ -2631,7 +2642,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint64_type, jl_uint8_type, jl_bool_type, - jl_bool_type), + jl_bool_type, + jl_uint16_type), jl_emptysvec, 0, 1, 6); jl_value_t *listt = jl_new_struct(jl_uniontype_type, jl_task_type, jl_nothing_type); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 10c204e1c50a6..9bb6622209ae2 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -4119,7 +4119,7 @@ f(x) = yt(x) (cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals)))))))) -(define (closure-convert e) (cl-convert e #f #f #f #f #f #f #f)) +(define (closure-convert e) (cl-convert e #f #f (table) (table) #f #f #f)) ;; pass 5: convert to linear IR @@ -4219,17 +4219,21 @@ f(x) = yt(x) (loop (cdr s)))))) `(pop_exception ,restore-token)))) (define (emit-return x) - (define (actually-return x) - (let* ((x (if rett - (compile (convert-for-type-decl x rett) '() #t #f) - x)) - (tmp (if ((if (null? catch-token-stack) valid-ir-return? simple-atom?) x) + (define (emit- x) + (let* ((tmp (if ((if (null? catch-token-stack) valid-ir-return? simple-atom?) x) #f (make-ssavalue)))) - (if tmp (emit `(= ,tmp ,x))) + (if tmp + (begin (emit `(= ,tmp ,x)) tmp) + x))) + (define (actually-return x) + (let* ((x (if rett + (compile (convert-for-type-decl (emit- x) rett) '() #t #f) + x)) + (x (emit- x))) (let ((pexc (pop-exc-expr catch-token-stack '()))) (if pexc (emit pexc))) - (emit `(return ,(or tmp x))))) + (emit `(return ,x)))) (if x (if (> handler-level 0) (let ((tmp (cond ((and (simple-atom? x) (or (not (ssavalue? x)) (not finally-handler))) #f) diff --git a/src/julia.h b/src/julia.h index 3153b87c3a9b9..702175d2cc86d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1601,16 +1601,16 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int error); +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); -JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT); +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs JL_MAYBE_UNROOTED); JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); @@ -1635,6 +1635,7 @@ JL_DLLEXPORT int jl_errno(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_set_errno(int e) JL_NOTSAFEPOINT; JL_DLLEXPORT int32_t jl_stat(const char *path, char *statbuf) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_cpu_threads(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_effective_threads(void) JL_NOTSAFEPOINT; JL_DLLEXPORT long jl_getpagesize(void) JL_NOTSAFEPOINT; JL_DLLEXPORT long jl_getallocationgranularity(void) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_is_debugbuild(void) JL_NOTSAFEPOINT; @@ -1881,12 +1882,12 @@ typedef struct _jl_task_t { _Atomic(uint8_t) _state; uint8_t sticky; // record whether this Task can be migrated to a new thread _Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with + // multiqueue priority + uint16_t priority; // hidden state: // id of owning thread - does not need to be defined until the task runs _Atomic(int16_t) tid; - // multiqueue priority - int16_t prio; // saved gc stack top for context switches jl_gcframe_t *gcstack; size_t world_age; diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index a4c1a2596b2ae..19b9660378c2c 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -517,8 +517,8 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, auto res = Intrinsic::matchIntrinsicSignature(newfType, TableRef, overloadTys); assert(res == Intrinsic::MatchIntrinsicTypes_Match); (void)res; - bool matchvararg = Intrinsic::matchIntrinsicVarArg(newfType->isVarArg(), TableRef); - assert(!matchvararg); + bool matchvararg = !Intrinsic::matchIntrinsicVarArg(newfType->isVarArg(), TableRef); + assert(matchvararg); (void)matchvararg; } auto newF = Intrinsic::getDeclaration(call->getModule(), ID, overloadTys); diff --git a/src/module.c b/src/module.c index 249d0d548cd43..b3e4b0584f024 100644 --- a/src/module.c +++ b/src/module.c @@ -172,7 +172,7 @@ static jl_binding_t *new_binding(jl_sym_t *name) } // get binding for assignment -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int error) +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) { JL_LOCK(&m->lock); jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); @@ -183,20 +183,23 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, if (b->owner == NULL) { b->owner = m; } - else if (error) { + else if (alloc) { JL_UNLOCK(&m->lock); jl_errorf("cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } } - else { + else if (alloc) { b = new_binding(var); b->owner = m; *bp = b; JL_GC_PROMISE_ROOTED(b); jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); } + else { + b = NULL; + } JL_UNLOCK(&m->lock); return b; @@ -207,16 +210,11 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, // NOTE: Must hold m->lock while calling these. #ifdef __clang_gcanalyzer__ jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; -jl_binding_t **_jl_get_module_binding_bp(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; #else static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT { return (jl_binding_t*)ptrhash_get(&m->bindings, var); } -static inline jl_binding_t **_jl_get_module_binding_bp(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT -{ - return (jl_binding_t**)ptrhash_bp(&m->bindings, var); -} #endif @@ -234,8 +232,9 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var) { JL_LOCK(&m->lock); - jl_binding_t **bp = _jl_get_module_binding_bp(m, var); + jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); jl_binding_t *b = *bp; + JL_GC_PROMISE_ROOTED(b); if (b != HT_NOTFOUND) { if (b->owner != m) { @@ -261,6 +260,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ b = new_binding(var); b->owner = m; *bp = b; + JL_GC_PROMISE_ROOTED(b); jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); } @@ -310,14 +310,14 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl !tempb->deprecated && !b->deprecated && !(tempb->constp && tempb->value && b->constp && b->value == tempb->value)) { if (warn) { + // mark this binding resolved (by creating it or setting the owner), to avoid repeating the warning + (void)jl_get_binding_wr(m, var, 1); JL_UNLOCK(&m->lock); jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", jl_symbol_name(owner->name), jl_symbol_name(imp->name), jl_symbol_name(var), jl_symbol_name(m->name)); - // mark this binding resolved, to avoid repeating the warning - (void)jl_get_binding_wr(m, var, 0); JL_LOCK(&m->lock); } return NULL; @@ -390,6 +390,11 @@ JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var) return ty ? ty : jl_nothing; } +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m, jl_sym_t *var) +{ + return jl_get_binding_wr(m, var, 1); +} + JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { return jl_get_binding_(m, var, NULL); @@ -665,14 +670,6 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) return b->value; } -JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) -{ - JL_TYPECHK(jl_set_global, module, (jl_value_t*)m); - JL_TYPECHK(jl_set_global, symbol, (jl_value_t*)var); - jl_binding_t *bp = jl_get_binding_wr(m, var, 1); - jl_checked_assignment(bp, val); -} - JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { jl_binding_t *bp = jl_get_binding_wr(m, var, 1); @@ -686,7 +683,7 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var return; } } - jl_value_t *old_ty = NULL; + jl_value_t *old_ty = NULL; jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); } jl_errorf("invalid redefinition of constant %s", @@ -807,9 +804,14 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) { jl_value_t *old_ty = NULL; - if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type) && !jl_isa(rhs, old_ty)) { - jl_errorf("cannot assign an incompatible value to the global %s.", - jl_symbol_name(b->name)); + if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type)) { + if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { + JL_GC_PUSH1(&rhs); + if (!jl_isa(rhs, old_ty)) + jl_errorf("cannot assign an incompatible value to the global %s.", + jl_symbol_name(b->name)); + JL_GC_POP(); + } } if (b->constp) { jl_value_t *old = NULL; diff --git a/src/partr.c b/src/partr.c index c8cc3245ebb4c..52c4a468c77c2 100644 --- a/src/partr.c +++ b/src/partr.c @@ -34,11 +34,9 @@ static const int16_t sleeping = 1; // information: These observations require sequentially-consistent fences to be inserted between each of those operational phases. // [^store_buffering_1]: These fences are used to avoid the cycle 2b -> 1a -> 1b -> 2a -> 2b where // * Dequeuer: -// * 1a: `jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping)` -// * 1b: `multiq_check_empty` returns true +// * 1: `jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping)` // * Enqueuer: -// * 2a: `multiq_insert` -// * 2b: `jl_atomic_load_relaxed(&ptls->sleep_check_state)` in `jl_wakeup_thread` returns `not_sleeping` +// * 2: `jl_atomic_load_relaxed(&ptls->sleep_check_state)` in `jl_wakeup_thread` returns `not_sleeping` // i.e., the dequeuer misses the enqueue and enqueuer misses the sleep state transition. @@ -67,187 +65,22 @@ JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int tid) JL_NOTSAFEPOINT extern int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_value_t *obj) JL_NOTSAFEPOINT; -// multiq -// --- - -/* a task heap */ -typedef struct taskheap_tag { - uv_mutex_t lock; - jl_task_t **tasks; - _Atomic(int32_t) ntasks; - _Atomic(int16_t) prio; -} taskheap_t; - -/* multiqueue parameters */ -static const int32_t heap_d = 8; -static const int heap_c = 2; - -/* size of each heap */ -static const int tasks_per_heap = 65536; // TODO: this should be smaller by default, but growable! - -/* the multiqueue's heaps */ -static taskheap_t *heaps; -static int32_t heap_p; - -/* unbias state for the RNG */ -static uint64_t cong_unbias; - - -static inline void multiq_init(void) -{ - heap_p = heap_c * jl_n_threads; - heaps = (taskheap_t *)calloc(heap_p, sizeof(taskheap_t)); - for (int32_t i = 0; i < heap_p; ++i) { - uv_mutex_init(&heaps[i].lock); - heaps[i].tasks = (jl_task_t **)calloc(tasks_per_heap, sizeof(jl_task_t*)); - jl_atomic_store_relaxed(&heaps[i].ntasks, 0); - jl_atomic_store_relaxed(&heaps[i].prio, INT16_MAX); - } - unbias_cong(heap_p, &cong_unbias); -} - - -static inline void sift_up(taskheap_t *heap, int32_t idx) -{ - if (idx > 0) { - int32_t parent = (idx-1)/heap_d; - if (heap->tasks[idx]->prio < heap->tasks[parent]->prio) { - jl_task_t *t = heap->tasks[parent]; - heap->tasks[parent] = heap->tasks[idx]; - heap->tasks[idx] = t; - sift_up(heap, parent); - } - } -} - - -static inline void sift_down(taskheap_t *heap, int32_t idx) -{ - if (idx < jl_atomic_load_relaxed(&heap->ntasks)) { - for (int32_t child = heap_d*idx + 1; - child < tasks_per_heap && child <= heap_d*idx + heap_d; - ++child) { - if (heap->tasks[child] - && heap->tasks[child]->prio < heap->tasks[idx]->prio) { - jl_task_t *t = heap->tasks[idx]; - heap->tasks[idx] = heap->tasks[child]; - heap->tasks[child] = t; - sift_down(heap, child); - } - } - } -} - - -static inline int multiq_insert(jl_task_t *task, int16_t priority) -{ - jl_ptls_t ptls = jl_current_task->ptls; - uint64_t rn; - - task->prio = priority; - do { - rn = cong(heap_p, cong_unbias, &ptls->rngseed); - } while (uv_mutex_trylock(&heaps[rn].lock) != 0); - - if (jl_atomic_load_relaxed(&heaps[rn].ntasks) >= tasks_per_heap) { - uv_mutex_unlock(&heaps[rn].lock); - // multiq insertion failed, increase #tasks per heap - return -1; - } - - int32_t ntasks = jl_atomic_load_relaxed(&heaps[rn].ntasks); - jl_atomic_store_relaxed(&heaps[rn].ntasks, ntasks + 1); - heaps[rn].tasks[ntasks] = task; - sift_up(&heaps[rn], ntasks); - int16_t prio = jl_atomic_load_relaxed(&heaps[rn].prio); - if (task->prio < prio) - jl_atomic_store_relaxed(&heaps[rn].prio, task->prio); - uv_mutex_unlock(&heaps[rn].lock); - - return 0; -} +// parallel task runtime +// --- -static inline jl_task_t *multiq_deletemin(void) +JL_DLLEXPORT uint32_t jl_rand_ptls(uint32_t max, uint32_t unbias) { jl_ptls_t ptls = jl_current_task->ptls; - uint64_t rn1 = 0, rn2; - int32_t i; - int16_t prio1, prio2; - jl_task_t *task; - retry: - jl_gc_safepoint(); - for (i = 0; i < heap_p; ++i) { - rn1 = cong(heap_p, cong_unbias, &ptls->rngseed); - rn2 = cong(heap_p, cong_unbias, &ptls->rngseed); - prio1 = jl_atomic_load_relaxed(&heaps[rn1].prio); - prio2 = jl_atomic_load_relaxed(&heaps[rn2].prio); - if (prio1 > prio2) { - prio1 = prio2; - rn1 = rn2; - } - else if (prio1 == prio2 && prio1 == INT16_MAX) - continue; - if (uv_mutex_trylock(&heaps[rn1].lock) == 0) { - if (prio1 == jl_atomic_load_relaxed(&heaps[rn1].prio)) - break; - uv_mutex_unlock(&heaps[rn1].lock); - } - } - if (i == heap_p) - return NULL; - - task = heaps[rn1].tasks[0]; - if (!jl_set_task_tid(task, ptls->tid)) { - uv_mutex_unlock(&heaps[rn1].lock); - goto retry; - } - int32_t ntasks = jl_atomic_load_relaxed(&heaps[rn1].ntasks) - 1; - jl_atomic_store_relaxed(&heaps[rn1].ntasks, ntasks); - heaps[rn1].tasks[0] = heaps[rn1].tasks[ntasks]; - heaps[rn1].tasks[ntasks] = NULL; - prio1 = INT16_MAX; - if (ntasks > 0) { - sift_down(&heaps[rn1], 0); - prio1 = heaps[rn1].tasks[0]->prio; - } - jl_atomic_store_relaxed(&heaps[rn1].prio, prio1); - uv_mutex_unlock(&heaps[rn1].lock); - - return task; -} - - -void jl_gc_mark_enqueued_tasks(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) -{ - int32_t i, j; - for (i = 0; i < heap_p; ++i) - for (j = 0; j < jl_atomic_load_relaxed(&heaps[i].ntasks); ++j) - jl_gc_mark_queue_obj_explicit(gc_cache, sp, (jl_value_t *)heaps[i].tasks[j]); + // one-extend unbias back to 64-bits + return cong(max, -(uint64_t)-unbias, &ptls->rngseed); } - -static int multiq_check_empty(void) -{ - int32_t i; - for (i = 0; i < heap_p; ++i) { - if (jl_atomic_load_relaxed(&heaps[i].ntasks) != 0) - return 0; - } - return 1; -} - - - -// parallel task runtime -// --- - // initialize the threading infrastructure // (used only by the main thread) void jl_init_threadinginfra(void) { - /* initialize the synchronization trees pool and the multiqueue */ - multiq_init(); + /* initialize the synchronization trees pool */ sleep_threshold = DEFAULT_THREAD_SLEEP_THRESHOLD; char *cp = getenv(THREAD_SLEEP_THRESHOLD_NAME); @@ -299,18 +132,6 @@ void jl_threadfun(void *arg) } -// enqueue the specified task for execution -JL_DLLEXPORT int jl_enqueue_task(jl_task_t *task) -{ - char failed; - if (multiq_insert(task, task->prio) == -1) - failed = 1; - failed = 0; - JL_PROBE_RT_TASKQ_INSERT(jl_current_task->ptls, task); - return failed; -} - - int jl_running_under_rr(int recheck) { #ifdef _OS_LINUX_ @@ -439,21 +260,22 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) } -// get the next runnable task from the multiq +// get the next runnable task static jl_task_t *get_next_task(jl_value_t *trypoptask, jl_value_t *q) { jl_gc_safepoint(); - jl_value_t *args[2] = { trypoptask, q }; - jl_task_t *task = (jl_task_t*)jl_apply(args, 2); + jl_task_t *task = (jl_task_t*)jl_apply_generic(trypoptask, &q, 1); if (jl_typeis(task, jl_task_type)) { int self = jl_atomic_load_relaxed(&jl_current_task->tid); jl_set_task_tid(task, self); return task; } - task = multiq_deletemin(); - if (task) - JL_PROBE_RT_TASKQ_GET(jl_current_task->ptls, task); - return task; + return NULL; +} + +static int check_empty(jl_value_t *checkempty) +{ + return jl_apply_generic(checkempty, NULL, 0) == jl_true; } static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT @@ -468,7 +290,7 @@ static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT extern _Atomic(unsigned) _threadedregion; -JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) +JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, jl_value_t *checkempty) { jl_task_t *ct = jl_current_task; uint64_t start_cycles = 0; @@ -480,7 +302,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) // quick, race-y check to see if there seems to be any stuff in there jl_cpu_pause(); - if (!multiq_check_empty()) { + if (!check_empty(checkempty)) { start_cycles = 0; continue; } @@ -492,7 +314,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping); jl_fence(); // [^store_buffering_1] JL_PROBE_RT_SLEEP_CHECK_SLEEP(ptls); - if (!multiq_check_empty()) { // uses relaxed loads + if (!check_empty(checkempty)) { // uses relaxed loads if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us JL_PROBE_RT_SLEEP_CHECK_TASKQ_WAKE(ptls); diff --git a/src/staticdata.c b/src/staticdata.c index e72f29257ce19..4eeef024139c5 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -79,7 +79,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 154 +#define NUM_TAGS 155 // 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. @@ -164,13 +164,13 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_int64_type); INSERT_TAG(jl_bool_type); INSERT_TAG(jl_uint8_type); + INSERT_TAG(jl_uint16_type); INSERT_TAG(jl_uint32_type); INSERT_TAG(jl_uint64_type); INSERT_TAG(jl_char_type); INSERT_TAG(jl_weakref_type); INSERT_TAG(jl_int8_type); INSERT_TAG(jl_int16_type); - INSERT_TAG(jl_uint16_type); INSERT_TAG(jl_float16_type); INSERT_TAG(jl_float32_type); INSERT_TAG(jl_float64_type); @@ -253,6 +253,8 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin__typebody); INSERT_TAG(jl_builtin_donotdelete); INSERT_TAG(jl_builtin_getglobal); + INSERT_TAG(jl_builtin_setglobal); + // n.b. must update NUM_TAGS when you add something here // All optional tags must be placed at the end, so that we // don't accidentally have a `NULL` in the middle diff --git a/src/sys.c b/src/sys.c index 8f2be76e648c8..ffd43b36d4f68 100644 --- a/src/sys.c +++ b/src/sys.c @@ -436,7 +436,7 @@ JL_DLLEXPORT int jl_cpu_threads(void) JL_NOTSAFEPOINT #endif } -int jl_effective_threads(void) JL_NOTSAFEPOINT +JL_DLLEXPORT int jl_effective_threads(void) JL_NOTSAFEPOINT { int cpu = jl_cpu_threads(); int masksize = uv_cpumask_size(); diff --git a/src/task.c b/src/task.c index 1dd4e76b8ba1c..dbafa0ee29e0a 100644 --- a/src/task.c +++ b/src/task.c @@ -798,7 +798,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->gcstack = NULL; t->excstack = NULL; t->started = 0; - t->prio = -1; + t->priority = 0; jl_atomic_store_relaxed(&t->tid, t->copy_stack ? jl_atomic_load_relaxed(&ct->tid) : -1); // copy_stacks are always pinned since they can't be moved t->ptls = NULL; t->world_age = ct->world_age; diff --git a/src/toplevel.c b/src/toplevel.c index b9b2bb7535707..54a657df05db6 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -298,7 +298,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f } void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { - // create uninitialized mutable binding for "global x" decl + // create uninitialized mutable binding for "global x" decl sometimes or probably size_t i, l = jl_array_len(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); @@ -313,10 +313,13 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { gm = m; gs = (jl_sym_t*)arg; } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 0); - if (set_type && b) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + if (!jl_binding_resolved_p(gm, gs)) { + jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); + if (set_type) { + jl_value_t *old_ty = NULL; + // maybe set the type too, perhaps + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + } } } } @@ -589,7 +592,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym if (jl_binding_resolved_p(m, name)) { b = jl_get_binding(m, name); if ((!b->constp && b->owner != m) || (b->value && b->value != (jl_value_t*)import)) { - jl_errorf("importing %s into %s conflicts with an existing identifier", + jl_errorf("importing %s into %s conflicts with an existing global", jl_symbol_name(name), jl_symbol_name(m->name)); } } diff --git a/stdlib/Distributed/src/Distributed.jl b/stdlib/Distributed/src/Distributed.jl index d428a6df0e683..3bcbc7b67f60d 100644 --- a/stdlib/Distributed/src/Distributed.jl +++ b/stdlib/Distributed/src/Distributed.jl @@ -10,7 +10,7 @@ import Base: getindex, wait, put!, take!, fetch, isready, push!, length, hash, ==, kill, close, isopen, showerror # imports for use -using Base: Process, Semaphore, JLOptions, buffer_writes, @sync_add, +using Base: Process, Semaphore, JLOptions, buffer_writes, @async_unwrap, VERSION_STRING, binding_module, atexit, julia_exename, julia_cmd, AsyncGenerator, acquire, release, invokelatest, shell_escape_posixly, shell_escape_csh, @@ -76,7 +76,7 @@ function _require_callback(mod::Base.PkgId) # broadcast top-level (e.g. from Main) import/using from node 1 (only) @sync for p in procs() p == 1 && continue - @sync_add remotecall(p) do + @async_unwrap remotecall_wait(p) do Base.require(mod) nothing end diff --git a/stdlib/Distributed/src/clusterserialize.jl b/stdlib/Distributed/src/clusterserialize.jl index e37987c5bf875..0acd4ce68c45b 100644 --- a/stdlib/Distributed/src/clusterserialize.jl +++ b/stdlib/Distributed/src/clusterserialize.jl @@ -170,7 +170,7 @@ function deserialize_global_from_main(s::ClusterSerializer, sym) if sym_isconst ccall(:jl_set_const, Cvoid, (Any, Any, Any), Main, sym, v) else - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, sym, v) + setglobal!(Main, sym, v) end return nothing end @@ -243,7 +243,7 @@ An exception is raised if a global constant is requested to be cleared. """ function clear!(syms, pids=workers(); mod=Main) @sync for p in pids - @sync_add remotecall(clear_impl!, p, syms, mod) + @async_unwrap remotecall_wait(clear_impl!, p, syms, mod) end end clear!(sym::Symbol, pid::Int; mod=Main) = clear!([sym], [pid]; mod=mod) diff --git a/stdlib/Distributed/src/macros.jl b/stdlib/Distributed/src/macros.jl index 0a62fdd5439f0..a767c7a40d9c9 100644 --- a/stdlib/Distributed/src/macros.jl +++ b/stdlib/Distributed/src/macros.jl @@ -222,10 +222,10 @@ function remotecall_eval(m::Module, procs, ex) if pid == myid() run_locally += 1 else - @sync_add remotecall(Core.eval, pid, m, ex) + @async_unwrap remotecall_wait(Core.eval, pid, m, ex) end end - yield() # ensure that the remotecall_fetch have had a chance to start + yield() # ensure that the remotecalls have had a chance to start # execute locally last as we do not want local execution to block serialization # of the request to remote nodes. diff --git a/stdlib/Downloads.version b/stdlib/Downloads.version index 6553487f41cbc..b325171e6075d 100644 --- a/stdlib/Downloads.version +++ b/stdlib/Downloads.version @@ -1,4 +1,4 @@ DOWNLOADS_BRANCH = master -DOWNLOADS_SHA1 = 2a21b1536aec0219c6bdb78dbb6570fc31a40983 +DOWNLOADS_SHA1 = a7a034668e85f45169568cc0ee47aa43ab7dbd67 DOWNLOADS_GIT_URL := https://github.com/JuliaLang/Downloads.jl.git DOWNLOADS_TAR_URL = https://api.github.com/repos/JuliaLang/Downloads.jl/tarball/$1 diff --git a/stdlib/LinearAlgebra/docs/src/index.md b/stdlib/LinearAlgebra/docs/src/index.md index 47dc7e5d49eaf..07cdded9eae28 100644 --- a/stdlib/LinearAlgebra/docs/src/index.md +++ b/stdlib/LinearAlgebra/docs/src/index.md @@ -183,10 +183,10 @@ as well as whether hooks to various optimized methods for them in LAPACK are ava |:----------------------------- |:--- |:--- |:--- |:--- |:----------------------------------------------------------- | | [`Symmetric`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | | [`Hermitian`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | -| [`UpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`UnitUpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`LowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`UnitLowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | +| [`UpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`UnitUpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`LowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`UnitLowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | | [`UpperHessenberg`](@ref) | | | | MM | [`inv`](@ref), [`det`](@ref) | | [`SymTridiagonal`](@ref) | M | M | MS | MV | [`eigmax`](@ref), [`eigmin`](@ref) | | [`Tridiagonal`](@ref) | M | M | MS | MV | | diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index 243553ebc64c6..317ed15af770c 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -168,21 +168,19 @@ end function Matrix{T}(A::Bidiagonal) where T n = size(A, 1) B = zeros(T, n, n) - if n == 0 - return B - end - for i = 1:n - 1 + n == 0 && return B + @inbounds for i = 1:n - 1 B[i,i] = A.dv[i] if A.uplo == 'U' - B[i, i + 1] = A.ev[i] + B[i,i+1] = A.ev[i] else - B[i + 1, i] = A.ev[i] + B[i+1,i] = A.ev[i] end end B[n,n] = A.dv[n] return B end -Matrix(A::Bidiagonal{T}) where {T} = Matrix{T}(A) +Matrix(A::Bidiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(A) Array(A::Bidiagonal) = Matrix(A) promote_rule(::Type{Matrix{T}}, ::Type{<:Bidiagonal{S}}) where {T,S} = @isdefined(T) && @isdefined(S) ? Matrix{promote_type(T,S)} : Matrix diff --git a/stdlib/LinearAlgebra/src/blas.jl b/stdlib/LinearAlgebra/src/blas.jl index 9fff233e9914d..b2aa3ef3250f0 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -490,7 +490,9 @@ for (fname, elty) in ((:daxpy_,:Float64), end end end -function axpy!(alpha::Number, x::AbstractArray{T}, y::AbstractArray{T}) where T<:BlasFloat + +#TODO: replace with `x::AbstractArray{T}` once we separate `BLAS.axpy!` and `LinearAlgebra.axpy!` +function axpy!(alpha::Number, x::Union{DenseArray{T},StridedVector{T}}, y::Union{DenseArray{T},StridedVector{T}}) where T<:BlasFloat if length(x) != length(y) throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end @@ -562,7 +564,8 @@ for (fname, elty) in ((:daxpby_,:Float64), (:saxpby_,:Float32), end end -function axpby!(alpha::Number, x::AbstractArray{T}, beta::Number, y::AbstractArray{T}) where T<:BlasFloat +#TODO: replace with `x::AbstractArray{T}` once we separate `BLAS.axpby!` and `LinearAlgebra.axpby!` +function axpby!(alpha::Number, x::Union{DenseArray{T},AbstractVector{T}}, beta::Number, y::Union{DenseArray{T},AbstractVector{T}},) where T<:BlasFloat require_one_based_indexing(x, y) if length(x) != length(y) throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) diff --git a/stdlib/LinearAlgebra/src/dense.jl b/stdlib/LinearAlgebra/src/dense.jl index ffcd9e64e0752..249010adb4e5c 100644 --- a/stdlib/LinearAlgebra/src/dense.jl +++ b/stdlib/LinearAlgebra/src/dense.jl @@ -257,6 +257,8 @@ Vector `kv.second` will be placed on the `kv.first` diagonal. By default the matrix is square and its size is inferred from `kv`, but a non-square size `m`×`n` (padded with zeros as needed) can be specified by passing `m,n` as the first arguments. +For repeated diagonal indices `kv.first` the values in the corresponding +vectors `kv.second` will be added. `diagm` constructs a full matrix; if you want storage-efficient versions with fast arithmetic, see [`Diagonal`](@ref), [`Bidiagonal`](@ref) @@ -277,6 +279,13 @@ julia> diagm(1 => [1,2,3], -1 => [4,5]) 4 0 2 0 0 5 0 3 0 0 0 0 + +julia> diagm(1 => [1,2,3], 1 => [1,2,3]) +4×4 Matrix{Int64}: + 0 2 0 0 + 0 0 4 0 + 0 0 0 6 + 0 0 0 0 ``` """ diagm(kv::Pair{<:Integer,<:AbstractVector}...) = _diagm(nothing, kv...) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 4b7d9bd9d4af1..5d17049cfa4e1 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -77,8 +77,16 @@ Diagonal{T}(D::Diagonal{T}) where {T} = D Diagonal{T}(D::Diagonal) where {T} = Diagonal{T}(D.diag) AbstractMatrix{T}(D::Diagonal) where {T} = Diagonal{T}(D) -Matrix(D::Diagonal) = diagm(0 => D.diag) -Array(D::Diagonal) = Matrix(D) +Matrix(D::Diagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(D) +Array(D::Diagonal{T}) where {T} = Matrix(D) +function Matrix{T}(D::Diagonal) where {T} + n = size(D, 1) + B = zeros(T, n, n) + @inbounds for i in 1:n + B[i,i] = D.diag[i] + end + return B +end """ Diagonal{T}(undef, n) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index aa38419614b73..799fa53f0b00f 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -1539,9 +1539,9 @@ julia> det(M) 2.0 ``` """ -function det(A::AbstractMatrix{T}) where T +function det(A::AbstractMatrix{T}) where {T} if istriu(A) || istril(A) - S = typeof((one(T)*zero(T) + zero(T))/one(T)) + S = promote_type(T, typeof((one(T)*zero(T) + zero(T))/one(T))) return convert(S, det(UpperTriangular(A))) end return det(lu(A; check = false)) diff --git a/stdlib/LinearAlgebra/src/qr.jl b/stdlib/LinearAlgebra/src/qr.jl index 16e066ed1e030..4e1cc83b468f5 100644 --- a/stdlib/LinearAlgebra/src/qr.jl +++ b/stdlib/LinearAlgebra/src/qr.jl @@ -582,8 +582,9 @@ size(F::Union{QR,QRCompactWY,QRPivoted}) = size(getfield(F, :factors)) size(Q::AbstractQ, dim::Integer) = size(getfield(Q, :factors), dim == 2 ? 1 : dim) size(Q::AbstractQ) = size(Q, 1), size(Q, 2) -copy(Q::AbstractQ{T}) where {T} = lmul!(Q, Matrix{T}(I, size(Q))) -getindex(Q::AbstractQ, inds...) = copy(Q)[inds...] +copymutable(Q::AbstractQ{T}) where {T} = lmul!(Q, Matrix{T}(I, size(Q))) +copy(Q::AbstractQ) = copymutable(Q) +getindex(Q::AbstractQ, inds...) = copymutable(Q)[inds...] getindex(Q::AbstractQ, ::Colon, ::Colon) = copy(Q) function getindex(Q::AbstractQ, ::Colon, j::Int) diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 39b62d5e3ca03..beac0c524f2f4 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -292,16 +292,65 @@ function (-)(A::UniformScaling, B::Diagonal{<:Number}) Diagonal(A.λ .- B.diag) end -rmul!(A::AbstractTriangular, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = - rmul!(full!(A), adjB) -*(A::AbstractTriangular, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = - *(copyto!(similar(parent(A)), A), adjB) -*(A::BiTriSym, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}) = - rmul!(copyto!(Array{promote_type(eltype(A), eltype(adjB))}(undef, size(A)...), A), adjB) -*(adjA::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}, B::Diagonal) = - lmul!(adjA, copyto!(Array{promote_type(eltype(adjA), eltype(B))}(undef, size(B)...), B)) -*(adjA::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}, B::BiTriSym) = - lmul!(adjA, copyto!(Array{promote_type(eltype(adjA), eltype(B))}(undef, size(B)...), B)) +lmul!(Q::AbstractQ, B::AbstractTriangular) = lmul!(Q, full!(B)) +lmul!(Q::QRPackedQ, B::AbstractTriangular) = lmul!(Q, full!(B)) # disambiguation +lmul!(Q::Adjoint{<:Any,<:AbstractQ}, B::AbstractTriangular) = lmul!(Q, full!(B)) +lmul!(Q::Adjoint{<:Any,<:QRPackedQ}, B::AbstractTriangular) = lmul!(Q, full!(B)) # disambiguation + +function _qlmul(Q::AbstractQ, B) + TQB = promote_type(eltype(Q), eltype(B)) + if size(Q.factors, 1) == size(B, 1) + Bnew = Matrix{TQB}(B) + elseif size(Q.factors, 2) == size(B, 1) + Bnew = [Matrix{TQB}(B); zeros(TQB, size(Q.factors, 1) - size(B,1), size(B, 2))] + else + throw(DimensionMismatch("first dimension of matrix must have size either $(size(Q.factors, 1)) or $(size(Q.factors, 2))")) + end + lmul!(convert(AbstractMatrix{TQB}, Q), Bnew) +end +function _qlmul(adjQ::Adjoint{<:Any,<:AbstractQ}, B) + TQB = promote_type(eltype(adjQ), eltype(B)) + lmul!(adjoint(convert(AbstractMatrix{TQB}, parent(adjQ))), Matrix{TQB}(B)) +end + +*(Q::AbstractQ, B::AbstractTriangular) = _qlmul(Q, B) +*(Q::Adjoint{<:Any,<:AbstractQ}, B::AbstractTriangular) = _qlmul(Q, B) +*(Q::AbstractQ, B::BiTriSym) = _qlmul(Q, B) +*(Q::Adjoint{<:Any,<:AbstractQ}, B::BiTriSym) = _qlmul(Q, B) +*(Q::AbstractQ, B::Diagonal) = _qlmul(Q, B) +*(Q::Adjoint{<:Any,<:AbstractQ}, B::Diagonal) = _qlmul(Q, B) + +rmul!(A::AbstractTriangular, Q::AbstractQ) = rmul!(full!(A), Q) +rmul!(A::AbstractTriangular, Q::Adjoint{<:Any,<:AbstractQ}) = rmul!(full!(A), Q) + +function _qrmul(A, Q::AbstractQ) + TAQ = promote_type(eltype(A), eltype(Q)) + return rmul!(Matrix{TAQ}(A), convert(AbstractMatrix{TAQ}, Q)) +end +function _qrmul(A, adjQ::Adjoint{<:Any,<:AbstractQ}) + Q = adjQ.parent + TAQ = promote_type(eltype(A), eltype(Q)) + if size(A,2) == size(Q.factors, 1) + Anew = Matrix{TAQ}(A) + elseif size(A,2) == size(Q.factors,2) + Anew = [Matrix{TAQ}(A) zeros(TAQ, size(A, 1), size(Q.factors, 1) - size(Q.factors, 2))] + else + throw(DimensionMismatch("matrix A has dimensions $(size(A)) but matrix B has dimensions $(size(Q))")) + end + return rmul!(Anew, adjoint(convert(AbstractMatrix{TAQ}, Q))) +end + +*(A::AbstractTriangular, Q::AbstractQ) = _qrmul(A, Q) +*(A::AbstractTriangular, Q::Adjoint{<:Any,<:AbstractQ}) = _qrmul(A, Q) +*(A::BiTriSym, Q::AbstractQ) = _qrmul(A, Q) +*(A::BiTriSym, Q::Adjoint{<:Any,<:AbstractQ}) = _qrmul(A, Q) +*(A::Diagonal, Q::AbstractQ) = _qrmul(A, Q) +*(A::Diagonal, Q::Adjoint{<:Any,<:AbstractQ}) = _qrmul(A, Q) + +*(Q::AbstractQ, B::AbstractQ) = _qlmul(Q, B) +*(Q::Adjoint{<:Any,<:AbstractQ}, B::AbstractQ) = _qrmul(Q, B) +*(Q::AbstractQ, B::Adjoint{<:Any,<:AbstractQ}) = _qlmul(Q, B) +*(Q::Adjoint{<:Any,<:AbstractQ}, B::Adjoint{<:Any,<:AbstractQ}) = _qrmul(Q, B) # fill[stored]! methods fillstored!(A::Diagonal, x) = (fill!(A.diag, x); A) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 5a3c7612f6784..e5c31856d3f0a 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -134,7 +134,7 @@ function Matrix{T}(M::SymTridiagonal) where T Mf[n,n] = symmetric(M.dv[n], :U) return Mf end -Matrix(M::SymTridiagonal{T}) where {T} = Matrix{T}(M) +Matrix(M::SymTridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) Array(M::SymTridiagonal) = Matrix(M) size(A::SymTridiagonal) = (length(A.dv), length(A.dv)) @@ -571,18 +571,19 @@ function size(M::Tridiagonal, d::Integer) end end -function Matrix{T}(M::Tridiagonal{T}) where T +function Matrix{T}(M::Tridiagonal) where {T} A = zeros(T, size(M)) - for i = 1:length(M.d) + n = length(M.d) + n == 0 && return A + for i in 1:n-1 A[i,i] = M.d[i] - end - for i = 1:length(M.d)-1 A[i+1,i] = M.dl[i] A[i,i+1] = M.du[i] end + A[n,n] = M.d[n] A end -Matrix(M::Tridiagonal{T}) where {T} = Matrix{T}(M) +Matrix(M::Tridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) Array(M::Tridiagonal) = Matrix(M) similar(M::Tridiagonal, ::Type{T}) where {T} = Tridiagonal(similar(M.dl, T), similar(M.d, T), similar(M.du, T)) diff --git a/stdlib/LinearAlgebra/test/generic.jl b/stdlib/LinearAlgebra/test/generic.jl index b56edf9439fe0..e2dcc30791900 100644 --- a/stdlib/LinearAlgebra/test/generic.jl +++ b/stdlib/LinearAlgebra/test/generic.jl @@ -76,6 +76,35 @@ n = 5 # should be odd @test logabsdet(x)[1] ≈ logabsdet(X)[1] @test logabsdet(x)[2] ≈ logabsdet(X)[2] end + + @testset "det with nonstandard Number type" begin + struct MyDual{T<:Real} <: Real + val::T + eps::T + end + Base.:+(x::MyDual, y::MyDual) = MyDual(x.val + y.val, x.eps + y.eps) + Base.:*(x::MyDual, y::MyDual) = MyDual(x.val * y.val, x.eps * y.val + y.eps * x.val) + Base.:/(x::MyDual, y::MyDual) = x.val / y.val + Base.:(==)(x::MyDual, y::MyDual) = x.val == y.val && x.eps == y.eps + Base.zero(::MyDual{T}) where {T} = MyDual(zero(T), zero(T)) + Base.zero(::Type{MyDual{T}}) where {T} = MyDual(zero(T), zero(T)) + Base.one(::MyDual{T}) where {T} = MyDual(one(T), zero(T)) + Base.one(::Type{MyDual{T}}) where {T} = MyDual(one(T), zero(T)) + # the following line is required for BigFloat, IDK why it doesn't work via + # promote_rule like for all other types + Base.promote_type(::Type{MyDual{BigFloat}}, ::Type{BigFloat}) = MyDual{BigFloat} + Base.promote_rule(::Type{MyDual{T}}, ::Type{S}) where {T,S<:Real} = + MyDual{promote_type(T, S)} + Base.promote_rule(::Type{MyDual{T}}, ::Type{MyDual{S}}) where {T,S} = + MyDual{promote_type(T, S)} + Base.convert(::Type{MyDual{T}}, x::MyDual) where {T} = + MyDual(convert(T, x.val), convert(T, x.eps)) + if elty <: Real + @show elty + @show istriu(triu(MyDual.(A, zero(A)))) + @test det(triu(MyDual.(A, zero(A)))) isa MyDual + end + end end @testset "diag" begin @@ -295,6 +324,14 @@ end @test LinearAlgebra.axpy!(α, x, rx, y, ry) == [1 1 1 1; 11 1 1 26] end +@testset "LinearAlgebra.axp(b)y! for non strides input" begin + a = rand(5, 5) + @test LinearAlgebra.axpby!(1, Hermitian(a), 1, zeros(size(a))) == Hermitian(a) + @test_broken LinearAlgebra.axpby!(1, 1.:5, 1, zeros(5)) == 1.:5 + @test LinearAlgebra.axpy!(1, Hermitian(a), zeros(size(a))) == Hermitian(a) + @test LinearAlgebra.axpy!(1, 1.:5, zeros(5)) == 1.:5 +end + @testset "norm and normalize!" begin vr = [3.0, 4.0] for Tr in (Float32, Float64) diff --git a/stdlib/LinearAlgebra/test/qr.jl b/stdlib/LinearAlgebra/test/qr.jl index f9acbdb376465..a7b24f08385f2 100644 --- a/stdlib/LinearAlgebra/test/qr.jl +++ b/stdlib/LinearAlgebra/test/qr.jl @@ -449,6 +449,12 @@ end @test Q2[:, :] ≈ M[:, :] @test Q2[:, :, :] ≈ M[:, :, :] end + # Check that getindex works if copy returns itself (#44729) + struct MyIdentity{T} <: LinearAlgebra.AbstractQ{T} end + Base.size(::MyIdentity, dim::Integer) = dim in (1,2) ? 2 : 1 + Base.copy(J::MyIdentity) = J + LinearAlgebra.lmul!(::MyIdentity{T}, M::Array{T}) where {T} = M + @test MyIdentity{Float64}()[1,:] == [1.0, 0.0] end end # module TestQR diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 624868db9ba44..234f9f472557b 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -104,6 +104,28 @@ Random.seed!(1) @test LowerTriangular(C) == LowerTriangular(Cdense) end end + + @testset "Matrix constructor for !isa(zero(T), T)" begin + # the following models JuMP.jl's VariableRef and AffExpr, resp. + struct TypeWithoutZero end + struct TypeWithZero end + Base.promote_rule(::Type{TypeWithoutZero}, ::Type{TypeWithZero}) = TypeWithZero + Base.convert(::Type{TypeWithZero}, ::TypeWithoutZero) = TypeWithZero() + Base.zero(::Type{<:Union{TypeWithoutZero, TypeWithZero}}) = TypeWithZero() + LinearAlgebra.symmetric(::TypeWithoutZero, ::Symbol) = TypeWithoutZero() + Base.transpose(::TypeWithoutZero) = TypeWithoutZero() + d = fill(TypeWithoutZero(), 3) + du = fill(TypeWithoutZero(), 2) + dl = fill(TypeWithoutZero(), 2) + D = Diagonal(d) + Bu = Bidiagonal(d, du, :U) + Bl = Bidiagonal(d, dl, :L) + Tri = Tridiagonal(dl, d, du) + Sym = SymTridiagonal(d, dl) + for M in (D, Bu, Bl, Tri, Sym) + @test Matrix(M) == zeros(TypeWithZero, 3, 3) + end + end end @testset "Binary ops among special types" begin @@ -188,16 +210,21 @@ end @testset "Triangular Types and QR" begin - for typ in [UpperTriangular,LowerTriangular,LinearAlgebra.UnitUpperTriangular,LinearAlgebra.UnitLowerTriangular] + for typ in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) a = rand(n,n) atri = typ(a) + matri = Matrix(atri) b = rand(n,n) qrb = qr(b, ColumnNorm()) - @test *(atri, adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' - @test rmul!(copy(atri), adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' + @test atri * qrb.Q ≈ matri * qrb.Q ≈ rmul!(copy(atri), qrb.Q) + @test atri * qrb.Q' ≈ matri * qrb.Q' ≈ rmul!(copy(atri), qrb.Q') + @test qrb.Q * atri ≈ qrb.Q * matri ≈ lmul!(qrb.Q, copy(atri)) + @test qrb.Q' * atri ≈ qrb.Q' * matri ≈ lmul!(qrb.Q', copy(atri)) qrb = qr(b, NoPivot()) - @test *(atri, adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' - @test rmul!(copy(atri), adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' + @test atri * qrb.Q ≈ matri * qrb.Q ≈ rmul!(copy(atri), qrb.Q) + @test atri * qrb.Q' ≈ matri * qrb.Q' ≈ rmul!(copy(atri), qrb.Q') + @test qrb.Q * atri ≈ qrb.Q * matri ≈ lmul!(qrb.Q, copy(atri)) + @test qrb.Q' * atri ≈ qrb.Q' * matri ≈ lmul!(qrb.Q', copy(atri)) end end @@ -421,19 +448,18 @@ end end @testset "BiTriSym*Q' and Q'*BiTriSym" begin - dl = [1, 1, 1]; - d = [1, 1, 1, 1]; - Tri = Tridiagonal(dl, d, dl) + dl = [1, 1, 1] + d = [1, 1, 1, 1] + D = Diagonal(d) Bi = Bidiagonal(d, dl, :L) + Tri = Tridiagonal(dl, d, dl) Sym = SymTridiagonal(d, dl) F = qr(ones(4, 1)) A = F.Q' - @test Tri*A ≈ Matrix(Tri)*A - @test A*Tri ≈ A*Matrix(Tri) - @test Bi*A ≈ Matrix(Bi)*A - @test A*Bi ≈ A*Matrix(Bi) - @test Sym*A ≈ Matrix(Sym)*A - @test A*Sym ≈ A*Matrix(Sym) + for A in (F.Q, F.Q'), B in (D, Bi, Tri, Sym) + @test B*A ≈ Matrix(B)*A + @test A*B ≈ A*Matrix(B) + end end @testset "Ops on SymTridiagonal ev has the same length as dv" begin diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index b11dfb488c373..d3d5300c87527 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -140,9 +140,12 @@ function __init__() delay = 0.001 end init(n, delay, limitwarn = false) - PROFILE_PRINT_COND[] = Base.AsyncCondition() - ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), PROFILE_PRINT_COND[].handle) - errormonitor(Threads.@spawn(profile_printing_listener())) + @static if !Sys.iswindows() + # triggering a profile via signals is not implemented on windows + PROFILE_PRINT_COND[] = Base.AsyncCondition() + ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), PROFILE_PRINT_COND[].handle) + errormonitor(Threads.@spawn(profile_printing_listener())) + end end """ diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 2b3f8beb2beba..6ad05a6b707cb 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -170,7 +170,11 @@ let cmd = Base.julia_cmd() script = """ using Profile f(::Val) = GC.safepoint() - @profile for i = 1:10^3; f(Val(i)); end + @profile for i = 1:10^3 + println(i) + f(Val(i)) + end + println("done") print(Profile.len_data()) """ p = open(`$cmd -e $script`) @@ -184,7 +188,9 @@ let cmd = Base.julia_cmd() s = read(p, String) close(t) @test success(p) - @test parse(Int, s) > 100 + @test !isempty(s) + @test occursin("done", s) + @test parse(Int, split(s, '\n')[end]) > 100 end if Sys.isbsd() || Sys.islinux() diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 3308760046d4e..8e15bed447682 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -150,8 +150,7 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend) end value = Core.eval(Main, ast) backend.in_eval = false - # note: use jl_set_global to make sure value isn't passed through `expand` - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value) + setglobal!(Main, :ans, value) put!(backend.response_channel, Pair{Any, Bool}(value, false)) end break @@ -287,7 +286,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, Base.sigatomic_end() if iserr val = Base.scrub_repl_backtrace(val) - Base.istrivialerror(val) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val) + Base.istrivialerror(val) || setglobal!(Main, :err, val) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value @@ -304,13 +303,13 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, end end break - catch + catch ex if iserr println(errio) # an error during printing is likely to leave us mid-line println(errio, "SYSTEM (REPL): showing an error caused an error") try excs = Base.scrub_repl_backtrace(current_exceptions()) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs) + setglobal!(Main, :err, excs) Base.invokelatest(Base.display_error, errio, excs) catch e # at this point, only print the name of the type as a Symbol to diff --git a/stdlib/Random/docs/src/index.md b/stdlib/Random/docs/src/index.md index 059cd8f600e7d..0f7636cf2444f 100644 --- a/stdlib/Random/docs/src/index.md +++ b/stdlib/Random/docs/src/index.md @@ -70,6 +70,7 @@ Random.shuffle! ## Generators (creation and seeding) ```@docs +Random.default_rng Random.seed! Random.AbstractRNG Random.TaskLocalRNG diff --git a/stdlib/Random/src/RNGs.jl b/stdlib/Random/src/RNGs.jl index a50f633e68a9c..5a33cb97a36f0 100644 --- a/stdlib/Random/src/RNGs.jl +++ b/stdlib/Random/src/RNGs.jl @@ -355,6 +355,19 @@ end # GLOBAL_RNG currently uses TaskLocalRNG typeof_rng(::_GLOBAL_RNG) = TaskLocalRNG +""" + default_rng() -> rng + +Return the default global random number generator (RNG). + +!!! note + What the default RNG is is an implementation detail. Across different versions of + Julia, you should not expect the default RNG to be always the same, nor that it will + return the same stream of random numbers for a given seed. + +!!! compat "Julia 1.3" + This function was introduced in Julia 1.3. +""" @inline default_rng() = TaskLocalRNG() @inline default_rng(tid::Int) = TaskLocalRNG() diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 432fab1638dda..e3cd5f7905787 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -307,7 +307,7 @@ include("XoshiroSimd.jl") ## rand & rand! & seed! docstrings """ - rand([rng=GLOBAL_RNG], [S], [dims...]) + rand([rng=default_rng()], [S], [dims...]) Pick a random element or array of random elements from the set of values specified by `S`; `S` can be @@ -359,7 +359,7 @@ julia> rand(Float64, (2, 3)) rand """ - rand!([rng=GLOBAL_RNG], A, [S=eltype(A)]) + rand!([rng=default_rng()], A, [S=eltype(A)]) Populate the array `A` with random values. If `S` is specified (`S` can be a type or a collection, cf. [`rand`](@ref) for details), @@ -383,8 +383,8 @@ julia> rand!(rng, zeros(5)) rand! """ - seed!([rng=GLOBAL_RNG], seed) -> rng - seed!([rng=GLOBAL_RNG]) -> rng + seed!([rng=default_rng()], seed) -> rng + seed!([rng=default_rng()]) -> rng Reseed the random number generator: `rng` will give a reproducible sequence of numbers if and only if a `seed` is provided. Some RNGs diff --git a/stdlib/Random/src/misc.jl b/stdlib/Random/src/misc.jl index 0d6e06c444a09..ab6c796e5f539 100644 --- a/stdlib/Random/src/misc.jl +++ b/stdlib/Random/src/misc.jl @@ -11,7 +11,7 @@ function rand!(rng::AbstractRNG, B::BitArray, ::SamplerType{Bool}) end """ - bitrand([rng=GLOBAL_RNG], [dims...]) + bitrand([rng=default_rng()], [dims...]) Generate a `BitArray` of random boolean values. @@ -43,7 +43,7 @@ bitrand(dims::Integer...) = rand!(BitArray(undef, convert(Dims, dims))) ## randstring (often useful for temporary filenames/dirnames) """ - randstring([rng=GLOBAL_RNG], [chars], [len=8]) + randstring([rng=default_rng()], [chars], [len=8]) Create a random string of length `len`, consisting of characters from `chars`, which defaults to the set of upper- and lower-case letters @@ -126,7 +126,7 @@ function randsubseq!(r::AbstractRNG, S::AbstractArray, A::AbstractArray, p::Real end """ - randsubseq!([rng=GLOBAL_RNG,] S, A, p) + randsubseq!([rng=default_rng(),] S, A, p) Like [`randsubseq`](@ref), but the results are stored in `S` (which is resized as needed). @@ -154,7 +154,7 @@ randsubseq(r::AbstractRNG, A::AbstractArray{T}, p::Real) where {T} = randsubseq!(r, T[], A, p) """ - randsubseq([rng=GLOBAL_RNG,] A, p) -> Vector + randsubseq([rng=default_rng(),] A, p) -> Vector Return a vector consisting of a random subsequence of the given array `A`, where each element of `A` is included (in order) with independent probability `p`. (Complexity is @@ -182,7 +182,7 @@ ltm52(n::Int, mask::Int=nextpow(2, n)-1) = LessThan(n-1, Masked(mask, UInt52Raw( ## shuffle & shuffle! """ - shuffle!([rng=GLOBAL_RNG,] v::AbstractArray) + shuffle!([rng=default_rng(),] v::AbstractArray) In-place version of [`shuffle`](@ref): randomly permute `v` in-place, optionally supplying the random-number generator `rng`. @@ -228,7 +228,7 @@ end shuffle!(a::AbstractArray) = shuffle!(default_rng(), a) """ - shuffle([rng=GLOBAL_RNG,] v::AbstractArray) + shuffle([rng=default_rng(),] v::AbstractArray) Return a randomly permuted copy of `v`. The optional `rng` argument specifies a random number generator (see [Random Numbers](@ref)). @@ -260,7 +260,7 @@ shuffle(a::AbstractArray) = shuffle(default_rng(), a) ## randperm & randperm! """ - randperm([rng=GLOBAL_RNG,] n::Integer) + randperm([rng=default_rng(),] n::Integer) Construct a random permutation of length `n`. The optional `rng` argument specifies a random number generator (see [Random @@ -288,7 +288,7 @@ randperm(r::AbstractRNG, n::T) where {T <: Integer} = randperm!(r, Vector{T}(und randperm(n::Integer) = randperm(default_rng(), n) """ - randperm!([rng=GLOBAL_RNG,] A::Array{<:Integer}) + randperm!([rng=default_rng(),] A::Array{<:Integer}) Construct in `A` a random permutation of length `length(A)`. The optional `rng` argument specifies a random number generator (see @@ -328,7 +328,7 @@ randperm!(a::Array{<:Integer}) = randperm!(default_rng(), a) ## randcycle & randcycle! """ - randcycle([rng=GLOBAL_RNG,] n::Integer) + randcycle([rng=default_rng(),] n::Integer) Construct a random cyclic permutation of length `n`. The optional `rng` argument specifies a random number generator, see [Random Numbers](@ref). @@ -354,7 +354,7 @@ randcycle(r::AbstractRNG, n::T) where {T <: Integer} = randcycle!(r, Vector{T}(u randcycle(n::Integer) = randcycle(default_rng(), n) """ - randcycle!([rng=GLOBAL_RNG,] A::Array{<:Integer}) + randcycle!([rng=default_rng(),] A::Array{<:Integer}) Construct in `A` a random cyclic permutation of length `length(A)`. The optional `rng` argument specifies a random number generator, see diff --git a/stdlib/Random/src/normal.jl b/stdlib/Random/src/normal.jl index 6bb4cd2c36ce8..d7fe94f58fa57 100644 --- a/stdlib/Random/src/normal.jl +++ b/stdlib/Random/src/normal.jl @@ -10,7 +10,7 @@ ## randn """ - randn([rng=GLOBAL_RNG], [T=Float64], [dims...]) + randn([rng=default_rng()], [T=Float64], [dims...]) Generate a normally-distributed random number of type `T` with mean 0 and standard deviation 1. @@ -93,7 +93,7 @@ randn(rng::AbstractRNG, ::Type{Complex{T}}) where {T<:AbstractFloat} = ## randexp """ - randexp([rng=GLOBAL_RNG], [T=Float64], [dims...]) + randexp([rng=default_rng()], [T=Float64], [dims...]) Generate a random number of type `T` according to the exponential distribution with scale 1. @@ -141,7 +141,7 @@ end ## arrays & other scalar methods """ - randn!([rng=GLOBAL_RNG], A::AbstractArray) -> A + randn!([rng=default_rng()], A::AbstractArray) -> A Fill the array `A` with normally-distributed (mean 0, standard deviation 1) random numbers. Also see the [`rand`](@ref) function. @@ -162,7 +162,7 @@ julia> randn!(rng, zeros(5)) function randn! end """ - randexp!([rng=GLOBAL_RNG], A::AbstractArray) -> A + randexp!([rng=default_rng()], A::AbstractArray) -> A Fill the array `A` with random numbers following the exponential distribution (with scale 1). diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 1d93c36f6d7f0..343462a534a2f 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = aa51c9b82d952502139213715c9b077ec36c4623 +SPARSEARRAYS_SHA1 = 96820d3aba22dad0fbd2b4877e6a1f0f7af76721 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/sysimage.mk b/sysimage.mk index 1586eb7dbc16d..2d154672d8130 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -60,7 +60,7 @@ RELBUILDROOT := $(call rel_path,$(JULIAHOME)/base,$(BUILDROOT)/base)/ # <-- make $(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS) @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ $(call spawn,$(JULIA_EXECUTABLE)) -C "$(JULIA_CPU_TARGET)" --output-ji $(call cygpath_w,$@).tmp \ - --startup-file=no --warn-overwrite=yes -g0 -O0 compiler/compiler.jl) + --startup-file=no --warn-overwrite=yes -g$(BOOTSTRAP_DEBUG_LEVEL) -O0 compiler/compiler.jl) @mv $@.tmp $@ $(build_private_libdir)/sys.ji: $(build_private_libdir)/corecompiler.ji $(JULIAHOME)/VERSION $(BASE_SRCS) $(STDLIB_SRCS) diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 8d58672d30f09..e16573bfbe70a 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -218,7 +218,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # -t, --threads code = "print(Threads.nthreads())" - cpu_threads = ccall(:jl_cpu_threads, Int32, ()) + cpu_threads = ccall(:jl_effective_threads, Int32, ()) @test string(cpu_threads) == read(`$exename --threads auto -e $code`, String) == read(`$exename --threads=auto -e $code`, String) == diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 955dafeca4b7f..4b928d4f648e1 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4080,3 +4080,12 @@ end Base.Experimental.@force_compile Core.Compiler.return_type(+, NTuple{2, Rational}) end == Rational + +# https://github.com/JuliaLang/julia/issues/44763 +global x44763::Int = 0 +increase_x44763!(n) = (global x44763; x44763 += n) +invoke44763(x) = Base.@invoke increase_x44763!(x) +@test Base.return_types() do + invoke44763(42) +end |> only === Int +@test x44763 == 0 diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 045cf833944c2..6c77891bede5a 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -2,7 +2,9 @@ using Test using Base.Meta -using Core: PhiNode, SSAValue, GotoNode, PiNode, QuoteNode, ReturnNode, GotoIfNot +import Core: + CodeInfo, Argument, SSAValue, GotoNode, GotoIfNot, PiNode, PhiNode, + QuoteNode, ReturnNode include(normpath(@__DIR__, "irutils.jl")) @@ -12,7 +14,7 @@ include(normpath(@__DIR__, "irutils.jl")) ## Test that domsort doesn't mangle single-argument phis (#29262) let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 Expr(:call, :opaque), @@ -47,7 +49,7 @@ end # test that we don't stack-overflow in SNCA with large functions. let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo code = Any[] N = 2^15 for i in 1:2:N @@ -73,30 +75,87 @@ end # SROA # ==== +import Core.Compiler: widenconst + +is_load_forwarded(src::CodeInfo) = !any(iscall((src, getfield)), src.code) +is_scalar_replaced(src::CodeInfo) = + is_load_forwarded(src) && !any(iscall((src, setfield!)), src.code) && !any(isnew, src.code) + +function is_load_forwarded(@nospecialize(T), src::CodeInfo) + for i in 1:length(src.code) + x = src.code[i] + if iscall((src, getfield), x) + widenconst(argextype(x.args[1], src)) <: T && return false + end + end + return true +end +function is_scalar_replaced(@nospecialize(T), src::CodeInfo) + is_load_forwarded(T, src) || return false + for i in 1:length(src.code) + x = src.code[i] + if iscall((src, setfield!), x) + widenconst(argextype(x.args[1], src)) <: T && return false + elseif isnew(x) + widenconst(argextype(SSAValue(i), src)) <: T && return false + end + end + return true +end + struct ImmutableXYZ; x; y; z; end mutable struct MutableXYZ; x; y; z; end +struct ImmutableOuter{T}; x::T; y::T; z::T; end +mutable struct MutableOuter{T}; x::T; y::T; z::T; end +struct ImmutableRef{T}; x::T; end +Base.getindex(r::ImmutableRef) = r.x +mutable struct SafeRef{T}; x::T; end +Base.getindex(s::SafeRef) = getfield(s, 1) +Base.setindex!(s::SafeRef, x) = setfield!(s, 1, x) + +# simple immutability +# ------------------- -# should optimize away very basic cases let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end +end +let src = code_typed1((Any,Any,Any)) do x, y, z + xyz = (x, y, z) + xyz[1], xyz[2], xyz[3] + end + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end end + +# simple mutability +# ----------------- + let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end end - -# should handle simple mutabilities let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) xyz.y = 42 xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=x=# Core.Argument(2), 42, #=x=# Core.Argument(4)] @@ -107,19 +166,23 @@ let src = code_typed1((Any,Any,Any)) do x, y, z xyz.x, xyz.z = xyz.z, xyz.x xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=z=# Core.Argument(4), #=y=# Core.Argument(3), #=x=# Core.Argument(2)] end end -# circumvent uninitialized fields as far as there is a solid `setfield!` definition + +# uninitialized fields +# -------------------- + +# safe cases let src = code_typed1() do r = Ref{Any}() r[] = 42 return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -131,7 +194,7 @@ let src = code_typed1((Bool,)) do cond return r[] end end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -142,7 +205,7 @@ let src = code_typed1((Bool,)) do cond end return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z r = Ref{Any}() @@ -157,7 +220,16 @@ let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z end return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) +end + +# unsafe cases +let src = code_typed1() do + r = Ref{Any}() + return r[] + end + @test count(isnew, src.code) == 1 + @test count(iscall((src, getfield)), src.code) == 1 end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -167,7 +239,9 @@ let src = code_typed1((Bool,)) do cond return r[] end # N.B. `r` should be allocated since `cond` might be `false` and then it will be thrown - @test any(isnew, src.code) + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 1 + @test count(iscall((src, getfield)), src.code) == 1 end let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y r = Ref{Any}() @@ -181,12 +255,16 @@ let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y return r[] end # N.B. `r` should be allocated since `c2` might be `false` and then it will be thrown - @test any(isnew, src.code) + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 2 + @test count(iscall((src, getfield)), src.code) == 1 end -# should include a simple alias analysis -struct ImmutableOuter{T}; x::T; y::T; z::T; end -mutable struct MutableOuter{T}; x::T; y::T; z::T; end +# aliased load forwarding +# ----------------------- +# TODO fix broken examples with EscapeAnalysis + +# OK: immutable(immutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) outer = ImmutableOuter(xyz, xyz, xyz) @@ -214,22 +292,21 @@ let src = code_typed1((Any,Any,Any)) do x, y, z end end -# FIXME our analysis isn't yet so powerful at this moment: may be unable to handle nested objects well -# OK: mutable(immutable(...)) case +# OK (mostly): immutable(mutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) t = (xyz,) v = t[1].x v, v, v end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) outer = ImmutableOuter(xyz, xyz, xyz) outer.x.x, outer.y.y, outer.z.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=y=# Core.Argument(4)] @@ -240,12 +317,27 @@ let # this is a simple end to end test case, which demonstrates allocation elimi # NOTE this test case isn't so robust and might be subject to future changes of the broadcasting implementation, # in that case you don't really need to stick to keeping this test case around simple_sroa(s) = broadcast(identity, Ref(s)) + let src = code_typed1(simple_sroa, (String,)) + @test is_scalar_replaced(src) + end s = Base.inferencebarrier("julia")::String simple_sroa(s) # NOTE don't hard-code `"julia"` in `@allocated` clause and make sure to execute the # compiled code for `simple_sroa`, otherwise everything can be folded even without SROA @test @allocated(simple_sroa(s)) == 0 end +let # FIXME: some nested example + src = code_typed1((Int,)) do x + Ref(Ref(x))[][] + end + @test_broken is_scalar_replaced(src) + + src = code_typed1((Int,)) do x + Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref((x)))))))))))[][][][][][][][][][] + end + @test_broken is_scalar_replaced(src) +end + # FIXME: immutable(mutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) @@ -314,6 +406,118 @@ let src = code_typed1(compute_points) @test !any(isnew, src.code) end +# preserve elimination +# -------------------- + +function ispreserved(@nospecialize(x)) + return function (@nospecialize(stmt),) + if Meta.isexpr(stmt, :foreigncall) + nccallargs = length(stmt.args[3]::Core.SimpleVector) + for pidx = (6+nccallargs):length(stmt.args) + if stmt.args[pidx] === x + return true + end + end + end + return false + end +end + +let src = code_typed1((String,)) do s + ccall(:some_ccall, Cint, (Ptr{String},), Ref(s)) + end + @test count(isnew, src.code) == 0 + @test any(ispreserved(#=s=#Core.Argument(2)), src.code) +end + +# if the mutable struct is directly used, we shouldn't eliminate it +let src = code_typed1() do + a = MutableXYZ(-512275808,882558299,-2133022131) + b = Int32(42) + ccall(:some_ccall, Cvoid, (MutableXYZ, Int32), a, b) + return a.x + end + @test count(isnew, src.code) == 1 +end + +# should eliminate allocation whose address isn't taked even if it has unintialized field(s) +mutable struct BadRef + x::String + y::String + BadRef(x) = new(x) +end +Base.cconvert(::Type{Ptr{BadRef}}, a::String) = BadRef(a) +Base.unsafe_convert(::Type{Ptr{BadRef}}, ar::BadRef) = Ptr{BadRef}(pointer_from_objref(ar.x)) +let src = code_typed1((String,)) do s + ccall(:jl_breakpoint, Cvoid, (Ptr{BadRef},), s) + end + @test count(isnew, src.code) == 0 + @test any(ispreserved(#=s=#Core.Argument(2)), src.code) +end + +# isdefined elimination +# --------------------- + +let src = code_typed1((Any,)) do a + r = Ref{Any}() + r[] = a + if isassigned(r) + return r[] + end + return nothing + end + @test is_scalar_replaced(src) +end + +let src = code_typed1((Bool, Any,)) do cnd, a + r = Ref{Any}() + if cnd + r[] = a # this `setfield!` shouldn't be eliminated + end + return isassigned(r) + end + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 1 +end + +callit(f, args...) = f(args...) +function isdefined_elim() + local arr::Vector{Any} + callit() do + arr = Any[] + end + return arr +end +let src = code_typed1(isdefined_elim) + @test is_scalar_replaced(src) +end +@test isdefined_elim() == Any[] + +function abmult(r::Int, x0) + if r < 0 + r = -r + end + f = x -> x * r + return @inline f(x0) +end +let src = code_typed1(abmult, (Int,Int)) + @test is_scalar_replaced(src) +end +@test abmult(-3, 3) == 9 + +function abmult2(r0::Int, x0) + r::Int = r0 + if r < 0 + r = -r + end + f = x -> x * r + return f(x0) +end +let src = code_typed1(abmult2, (Int,Int)) + @test is_scalar_replaced(src) +end +@test abmult2(-3, 3) == 9 + # comparison lifting # ================== @@ -454,7 +658,7 @@ end # A SSAValue after the compaction line let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 nothing, @@ -517,7 +721,7 @@ end let m = Meta.@lower 1 + 1 # Test that CFG simplify combines redundant basic blocks @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ Core.Compiler.GotoNode(2), Core.Compiler.GotoNode(3), @@ -542,7 +746,7 @@ end let m = Meta.@lower 1 + 1 # Test that CFG simplify doesn't mess up when chaining past return blocks @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ Core.Compiler.GotoIfNot(Core.Compiler.Argument(2), 3), Core.Compiler.GotoNode(4), @@ -572,7 +776,7 @@ let m = Meta.@lower 1 + 1 # Test that CFG simplify doesn't try to merge every block in a loop into # its predecessor @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # Block 1 Core.Compiler.GotoNode(2), diff --git a/test/core.jl b/test/core.jl index 394455681a9db..50332c1d8f6b6 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7360,6 +7360,29 @@ struct A43411{S, T} end @test isbitstype(A43411{(:a,), Tuple{Int}}) +# issue #44614 +struct T44614_1{T} + m::T +end +struct T44614_2{L} + tuple::NTuple{3, Int64} + T44614_2{L}(t::NTuple{3, Int64}) where {L} = new{sum(t)}(t) +end +struct T44614_3{L, N} + a::Tuple{T44614_2{L}} + param::NTuple{N, T44614_1} + T44614_3(a::Tuple{T44614_2{L}}, pars::NTuple{N, T44614_1}) where {L, N} = new{L, N}(a, pars) +end +@test sizeof((T44614_2{L} where L).body) == 24 +let T = T44614_3{L,2} where L + # these values are computable, but we currently don't know how to compute them properly + ex = ErrorException("Argument is an incomplete T44614_3 type and does not have a definite size.") + @test_throws ex sizeof(T.body) + @test_throws ex sizeof(T) + @test_throws BoundsError fieldoffset(T.body, 2) + @test fieldoffset(T{1}, 2) == 24 +end + # Issue #34206/34207 function mre34206(a, n) va = view(a, :) diff --git a/test/llvmcall2.jl b/test/llvmcall2.jl index cfd20d210bfd7..8926b962a35c6 100644 --- a/test/llvmcall2.jl +++ b/test/llvmcall2.jl @@ -37,10 +37,26 @@ function ceilfloor(x::Float64) end @test ceilfloor(7.4) == 8.0 -# support for calling external functions -begin - f() = ccall("time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) - @test_throws ErrorException f() +let err = ErrorException("llvmcall only supports intrinsic calls") + # support for calling external functions + @test_throws err @eval ccall("time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) g() = ccall("extern time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) g() + @test_throws err @eval ccall("extern llvm.floor", llvmcall, Float64, (Float64,), 0.0) + + # support for mangling + @test (@eval ccall("llvm.floor.f64", llvmcall, Float64, (Float64,), 0.0)) === 0.0 + @test (@eval ccall("llvm.floor", llvmcall, Float64, (Float64,), 0.0), + ccall("llvm.floor", llvmcall, Float32, (Float32,), 0.0)) === (0.0, 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float32, (Float64,), 0.0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float32, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float64, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float64, (Int,), 0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Int, (Int,), 0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Float64, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Float64, (Int,), 0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Int, (Int,), 0) + + @test_throws err (@eval ccall("llvm.floor.f64", llvmcall, Float64, (Float64, Float64...,), 0.0)) === 0.0 + @test_throws err (@eval ccall("llvm.floor", llvmcall, Float64, (Float64, Float64...,), 0.0)) === 0.0 end diff --git a/test/misc.jl b/test/misc.jl index f89c4daa4840a..9ea610ad659f0 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1017,10 +1017,11 @@ end @testset "exports of modules" begin for (_, mod) in Base.loaded_modules - for v in names(mod) - @test isdefined(mod, v) - end - end + mod === Main && continue # Main exports everything + for v in names(mod) + @test isdefined(mod, v) + end + end end @testset "ordering UUIDs" begin diff --git a/test/runtests.jl b/test/runtests.jl index aa9e101fa2182..4c9ac1cfd869c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -124,6 +124,15 @@ cd(@__DIR__) do Distributed.remotecall_eval(Main, workers(), revise_init_expr) end + println(""" + Running parallel tests with: + nworkers() = $(nworkers()) + nthreads() = $(Threads.nthreads()) + Sys.CPU_THREADS = $(Sys.CPU_THREADS) + Sys.total_memory() = $(Base.format_bytes(Sys.total_memory())) + Sys.free_memory() = $(Base.format_bytes(Sys.free_memory())) + """) + #pretty print the information about gc and mem usage testgroupheader = "Test" workerheader = "(Worker)" diff --git a/test/syntax.jl b/test/syntax.jl index 99e371b09dd5a..bb95c50ac439a 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1917,7 +1917,12 @@ f31404(a, b; kws...) = (a, b, values(kws)) # issue #28992 macro id28992(x) x end @test @id28992(1 .+ 2) == 3 -@test Meta.isexpr(Meta.lower(@__MODULE__, :(@id28992((.+)(a,b) = 0))), :error) +@test Meta.@lower(.+(a,b) = 0) == Expr(:error, "invalid function name \".+\"") +@test Meta.@lower((.+)(a,b) = 0) == Expr(:error, "invalid function name \"(.+)\"") +let m = @__MODULE__ + @test Meta.lower(m, :($m.@id28992(.+(a,b) = 0))) == Expr(:error, "invalid function name \"$(nameof(m)).:.+\"") + @test Meta.lower(m, :($m.@id28992((.+)(a,b) = 0))) == Expr(:error, "invalid function name \"(.$(nameof(m)).+)\"") +end @test @id28992([1] .< [2] .< [3]) == [true] @test @id28992(2 ^ -2) == 0.25 @test @id28992(2 .^ -2) == 0.25 @@ -3276,3 +3281,7 @@ end @test m.Foo.bar === 1 @test Core.get_binding_type(m.Foo, :bar) == Any end + +# issue 44723 +demo44723()::Any = Base.Experimental.@opaque () -> true ? 1 : 2 +@test demo44723()() == 1 diff --git a/test/threads_exec.jl b/test/threads_exec.jl index ca8ec03b685e4..9cd5992d90a74 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -588,12 +588,12 @@ function test_thread_too_few_iters() end test_thread_too_few_iters() -@testset "InvasiveLinkedList" begin - @test eltype(Base.InvasiveLinkedList{Integer}) == Integer +@testset "IntrusiveLinkedList" begin + @test eltype(Base.IntrusiveLinkedList{Integer}) == Integer @test eltype(Base.LinkedList{Integer}) == Integer - @test eltype(Base.InvasiveLinkedList{<:Integer}) == Any + @test eltype(Base.IntrusiveLinkedList{<:Integer}) == Any @test eltype(Base.LinkedList{<:Integer}) == Any - @test eltype(Base.InvasiveLinkedList{<:Base.LinkedListItem{Integer}}) == Any + @test eltype(Base.IntrusiveLinkedList{<:Base.LinkedListItem{Integer}}) == Any t = Base.LinkedList{Integer}() @test eltype(t) == Integer