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/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index c6760d01a61e8..9a0468e8e36ea 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -6,30 +6,43 @@ 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` / `isdefined` 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[]) function compute_live_ins(cfg::CFG, du::SSADefUse) - # filter out `isdefined` usages - return compute_live_ins(cfg, du.defs, filter(>(0), du.uses)) + 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, ...)` @@ -89,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) @@ -358,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 @@ -772,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) @@ -787,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 @@ -846,13 +854,13 @@ 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, -idx) + push!(defuse.uses, IsdefinedUse(idx)) else - push!(defuse.uses, idx) + push!(defuse.uses, GetfieldUse(idx)) end union!(mid, intermediaries) end @@ -923,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] @@ -934,10 +942,7 @@ 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 @@ -946,7 +951,13 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)] all_eliminated = all_forwarded = true for use in defuse.uses - stmt = ir[SSAValue(abs(use))][:inst] # == `getfield`/`isdefined` 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 @@ -985,15 +996,29 @@ 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 - if use > 0 # == `getfield` use - has_safe_def(ir, get_domtree(), allblocks, du, newidx, use) || @goto skip - else # == `isdefined` use - if has_safe_def(ir, get_domtree(), allblocks, du, newidx, -use) - ir[SSAValue(-use)][:inst] = true + 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 @@ -1003,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) @@ -1017,17 +1041,24 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse end # Now go through all uses and rewrite them for use in du.uses - if use > 0 # == `getfield` use - ir[SSAValue(use)][:inst] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use) - else # == `isdefined` use + 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 - end - 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)) + 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 @@ -1056,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/tfuncs.jl b/base/compiler/tfuncs.jl index b204556bed83d..88f335cc6d492 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1546,6 +1546,10 @@ function tuple_tfunc(argtypes::Vector{Any}) params[i] = typeof(x.val) else x = isvarargtype(x) ? x : widenconst(x) + # since there don't exist any values whose runtime type are `Tuple{Type{...}}`, + # here we should turn such `Type{...}`-parameters to valid parameters, e.g. + # (::Type{Int},) -> Tuple{DataType} (or PartialStruct for more accuracy) + # (::Union{Type{Int32},Type{Int64}}) -> Tuple{Type} if isType(x) anyinfo = true xparam = x.parameters[1] @@ -1554,6 +1558,8 @@ function tuple_tfunc(argtypes::Vector{Any}) else params[i] = Type end + elseif !isvarargtype(x) && hasintersect(x, Type) + params[i] = Union{x, Type} else params[i] = x end 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/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/src/codegen.cpp b/src/codegen.cpp index b7483d1e88504..ca51b8dcaf5d2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6346,7 +6346,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 +6452,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); } diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index cba609be50c5f..ac004cda653b7 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) \ 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 863e9c8de7cbb..4b5948583bf9c 100644 --- a/src/julia.h +++ b/src/julia.h @@ -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; 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/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/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index dfcbec69c6de2..317ed15af770c 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -180,7 +180,7 @@ function Matrix{T}(A::Bidiagonal) where T 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 47e87377e3bdb..66201249eab52 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -489,7 +489,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 @@ -561,7 +563,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 11f3fff9cb3e2..5d17049cfa4e1 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -77,8 +77,8 @@ 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{T}) where {T} = Matrix{T}(D) -Array(D::Diagonal{T}) where {T} = Matrix{T}(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) 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/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 4b1d3add5df5b..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)) @@ -583,7 +583,7 @@ function Matrix{T}(M::Tridiagonal) where {T} 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 9b094c267d41b..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 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/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..1ad9efae51b11 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1562,6 +1562,15 @@ end @test arraysize_tfunc(Vector, Float64) === Union{} @test arraysize_tfunc(String, Int) === Union{} +let tuple_tfunc + function tuple_tfunc(@nospecialize xs...) + return Core.Compiler.tuple_tfunc(Any[xs...]) + end + @test Core.Compiler.widenconst(tuple_tfunc(Type{Int})) === Tuple{DataType} + # https://github.com/JuliaLang/julia/issues/44705 + @test tuple_tfunc(Union{Type{Int32},Type{Int64}}) === Tuple{Type} +end + function f23024(::Type{T}, ::Int) where T 1 + 1 end @@ -2082,7 +2091,7 @@ let M = Module() obj = $(Expr(:new, M.BePartialStruct, 42, :cond)) r1 = getfield(obj, :cond) ? 0 : a # r1::Union{Nothing,Int}, not r1::Int (because PartialStruct doesn't wrap Conditional) a = $(gensym(:anyvar))::Any - r2 = getfield(obj, :cond) ? a : nothing # r2::Any, not r2::Const(nothing) (we don't need to worry about constrait invalidation here) + r2 = getfield(obj, :cond) ? a : nothing # r2::Any, not r2::Const(nothing) (we don't need to worry about constraint invalidation here) return r1, r2 # ::Tuple{Union{Nothing,Int},Any} end |> only end diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index d441c7ebc4889..6c77891bede5a 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -409,10 +409,25 @@ 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 @@ -425,6 +440,21 @@ let src = code_typed1() do @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 # --------------------- @@ -463,6 +493,31 @@ let src = code_typed1(isdefined_elim) 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 # ================== diff --git a/test/syntax.jl b/test/syntax.jl index 99e371b09dd5a..8793a3de83bf8 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3276,3 +3276,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