diff --git a/.buildkite/README.md b/.buildkite/README.md index 12887536b90c1..b3f74f2b23137 100644 --- a/.buildkite/README.md +++ b/.buildkite/README.md @@ -3,3 +3,5 @@ This directory contains the Buildkite configuration files for Base Julia CI. The rootfs image definitions are located in the [rootfs-images](https://github.com/JuliaCI/rootfs-images) repository. + +The documentation for the Base Julia CI setup is located in the [base-buildkite-docs](https://github.com/JuliaCI/base-buildkite-docs) repository. diff --git a/.buildkite/pipelines/main/misc/llvmpasses.yml b/.buildkite/pipelines/main/misc/llvmpasses.yml index eed2f957855bb..a012ace41acff 100644 --- a/.buildkite/pipelines/main/misc/llvmpasses.yml +++ b/.buildkite/pipelines/main/misc/llvmpasses.yml @@ -33,8 +33,8 @@ steps: - JuliaCI/julia#v1: version: 1.6 - staticfloat/sandbox#v1: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v3.1/package_linux.x86_64.tar.gz - rootfs_treehash: "8c33c341a864852629b8aac01a6eb6a79b73570e" + rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v3.8/package_linux.x86_64.tar.gz + rootfs_treehash: "84a323ae8fcc724f8ea5aca5901bbbf4bda3e519" uid: 1000 gid: 1000 workspaces: diff --git a/.buildkite/pipelines/main/platforms/linux64.yml b/.buildkite/pipelines/main/platforms/linux64.yml index 1e88db1144b4c..44b0ebe29f557 100644 --- a/.buildkite/pipelines/main/platforms/linux64.yml +++ b/.buildkite/pipelines/main/platforms/linux64.yml @@ -35,6 +35,9 @@ steps: make release make install + echo "--- Make sure that the working directory is clean" + if [ -z "$(git status --short)" ]; then echo "INFO: The working directory is clean."; else echo "ERROR: The working directory is dirty."; echo "Output of git status:"; git status; exit 1; fi + echo "--- Print Julia version info" ./julia -e 'using InteractiveUtils; InteractiveUtils.versioninfo()' diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000000000..35cde5cd5e854 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,4 @@ +coverage: + status: + project: off + patch: off diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6cab5b68b11e9..5fc00a73b47d2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,5 @@ -CODEOWNERS @JuliaLang/github-actions -/.github/ @JuliaLang/github-actions +CODEOWNERS @JuliaLang/github-actions +/.github/ @JuliaLang/github-actions /.buildkite/ @JuliaLang/github-actions + +/.github/workflows/statuses.yml @DilumAluthge diff --git a/.github/workflows/statuses.yml b/.github/workflows/statuses.yml new file mode 100644 index 0000000000000..97ec290abe013 --- /dev/null +++ b/.github/workflows/statuses.yml @@ -0,0 +1,74 @@ +# Please ping @DilumAluthge when making any changes to this file. + +# This is just a short-term solution until we have migrated all of CI to Buildkite. +# +# 1. TODO: delete this file once we have migrated all of CI to Buildkite. +# +# 2. TODO: disable GitHub Actions on the `JuliaLang/julia` repository once we have migrated all +# of CI to Buildkite. + +# Here are some steps that we take in this workflow file for security reasons: +# 1. We do not checkout any code. +# 2. We do not run any external actions. +# 3. We only give `GITHUB_TOKEN` the minimum necessary set of permissions. + +name: Statuses + +on: + push: + branches: + - 'master' + - 'release-*' + # When using the `pull_request_target` event, all PRs will get a `GITHUB_TOKEN` that has + # write permissions, even if the PR is from a fork. + # Therefore, for security reasons, we do not checkout any code in this workflow. + pull_request_target: + branches: + - 'master' + - 'release-*' + +# These are the permissions for the `GITHUB_TOKEN` token. +# We should only give the token the minimum necessary set of permissions. +permissions: + statuses: write + +jobs: + statuses: + name: statuses + runs-on: ubuntu-latest + if: github.repository == 'JuliaLang/julia' + strategy: + fail-fast: false + steps: + - run: echo "SHA=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV + if: github.event_name == 'pull_request_target' + + - run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV + if: github.event_name != 'pull_request_target' + + - run: echo "The SHA is ${{ env.SHA }}" + + # As we incrementally migrate individual jobs from Buildbot to Buildkite, we should + # remove them from the `context_list`. + - run: | + declare -a CONTEXT_LIST=( + "buildbot/tester_freebsd64" + "buildbot/tester_linux32" + "buildbot/tester_linux64" + "buildbot/tester_linuxaarch64" + "buildbot/tester_macos64" + "buildbot/tester_win32" + "buildbot/tester_win64" + ) + for CONTEXT in "${CONTEXT_LIST[@]}" + do + curl \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "{\"context\": \"$CONTEXT\", \"state\": \"$STATE\"}" \ + https://api.github.com/repos/JuliaLang/julia/statuses/${{ env.SHA }} + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + STATE: "pending" diff --git a/LICENSE.md b/LICENSE.md index e2b9c6606b1fd..79127224d049b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,82 +1,26 @@ -The Julia language is licensed under the MIT License. The "language" consists -of the compiler (the contents of src/), most of the standard library (base/), -and some utilities (most of the rest of the files in this repository). See below -for exceptions. +MIT License -> Copyright (c) 2009-2021: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, -> and other contributors: -> -> https://github.com/JuliaLang/julia/contributors -> -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: -> -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2009-2021: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors -Julia includes code from the following projects, which have their own licenses: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -- [crc32c.c](https://stackoverflow.com/questions/17645167/implementing-sse-4-2s-crc32c-in-software) (CRC-32c checksum code by Mark Adler) [[ZLib](https://opensource.org/licenses/Zlib)]. -- [LDC](https://github.com/ldc-developers/ldc/blob/master/LICENSE) (for ccall/cfunction ABI definitions) [BSD-3]. The portion of code that Julia uses from LDC is [BSD-3] licensed. -- [LLVM](https://releases.llvm.org/3.9.0/LICENSE.TXT) (for parts of src/jitlayers.cpp and src/disasm.cpp) [BSD-3, effectively] -- [MUSL](https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT) (for getopt implementation on Windows) [MIT] -- [MINGW](https://sourceforge.net/p/mingw/mingw-org-wsl/ci/legacy/tree/mingwrt/mingwex/dirname.c) (for dirname implementation on Windows) [MIT] -- [NetBSD](https://www.netbsd.org/about/redistribution.html) (for setjmp, longjmp, and strptime implementations on Windows) [BSD-3] -- [Python](https://docs.python.org/3/license.html) (for strtod and joinpath implementation on Windows) [BSD-3, effectively] -- [Google Benchmark](https://github.com/google/benchmark) (for cyclecount implementation) [Apache 2.0] +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -The following components included in Julia `Base` have their own separate licenses: +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- base/ryu/* [Boost] (see [ryu](https://github.com/ulfjack/ryu/blob/master/LICENSE-Boost)) -- base/grisu/* [BSD-3] (see [double-conversion](https://github.com/google/double-conversion/blob/master/LICENSE)) -- base/special/{exp,rem_pio2,hyperbolic}.jl [Freely distributable with preserved copyright notice] (see [FDLIBM](https://www.netlib.org/fdlibm)) +end of terms and conditions -The Julia language links to the following external libraries, which have their -own licenses: - -- [FEMTOLISP](https://github.com/JeffBezanson/femtolisp) [BSD-3] -- [LIBUNWIND](https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=blob_plain;f=LICENSE;hb=master) [MIT] -- [LIBUV](https://github.com/joyent/libuv/blob/master/LICENSE) [MIT] -- [LLVM](https://releases.llvm.org/6.0.0/LICENSE.TXT) [BSD-3, effectively] -- [UTF8PROC](https://github.com/JuliaStrings/utf8proc) [MIT] - -Julia's `stdlib` uses the following external libraries, which have their own licenses: - -- [DSFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/LICENSE.txt) [BSD-3] -- [OPENLIBM](https://github.com/JuliaMath/openlibm/blob/master/LICENSE.md) [MIT, BSD-2, ISC] -- [GMP](https://gmplib.org/manual/Copying.html#Copying) [LGPL3+ or GPL2+] -- [LIBGIT2](https://github.com/libgit2/libgit2/blob/development/COPYING) [GPL2+ with unlimited linking exception] -- [CURL](https://curl.haxx.se/docs/copyright.html) [MIT/X derivative] -- [LIBSSH2](https://github.com/libssh2/libssh2/blob/master/COPYING) [BSD-3] -- [MBEDTLS](https://tls.mbed.org/how-to-get) [either GPLv2 or Apache 2.0] -- [MPFR](https://www.mpfr.org/mpfr-current/mpfr.html#Copying) [LGPL3+] -- [OPENBLAS](https://raw.github.com/xianyi/OpenBLAS/master/LICENSE) [BSD-3] -- [LAPACK](https://netlib.org/lapack/LICENSE.txt) [BSD-3] -- [PCRE](https://www.pcre.org/licence.txt) [BSD-3] -- [SUITESPARSE](http://suitesparse.com) [mix of LGPL2+ and GPL2+; see individual module licenses] - -Julia's build process uses the following external tools: - -- [PATCHELF](https://nixos.org/patchelf.html) -- [OBJCONV](https://www.agner.org/optimize/#objconv) - -Julia bundles the following external programs and libraries: - -- [7-Zip](https://www.7-zip.org/license.txt) -- [ZLIB](https://zlib.net/zlib_license.html) - -On some platforms, distributions of Julia contain SSL certificate authority certificates, -released under the [Mozilla Public License](https://en.wikipedia.org/wiki/Mozilla_Public_License). +Please see THIRDPARTY.md for license information for other software used in this project. diff --git a/THIRDPARTY.md b/THIRDPARTY.md new file mode 100644 index 0000000000000..87304437183d6 --- /dev/null +++ b/THIRDPARTY.md @@ -0,0 +1,56 @@ +The Julia language is licensed under the MIT License (see `LICENSE.md`). The "language" consists +of the compiler (the contents of src/), most of the standard library (base/), +and some utilities (most of the rest of the files in this repository). See below +for exceptions. + +- [crc32c.c](https://stackoverflow.com/questions/17645167/implementing-sse-4-2s-crc32c-in-software) (CRC-32c checksum code by Mark Adler) [[ZLib](https://opensource.org/licenses/Zlib)]. +- [LDC](https://github.com/ldc-developers/ldc/blob/master/LICENSE) (for ccall/cfunction ABI definitions) [BSD-3]. The portion of code that Julia uses from LDC is [BSD-3] licensed. +- [LLVM](https://releases.llvm.org/3.9.0/LICENSE.TXT) (for parts of src/jitlayers.cpp and src/disasm.cpp) [BSD-3, effectively] +- [MUSL](https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT) (for getopt implementation on Windows) [MIT] +- [MINGW](https://sourceforge.net/p/mingw/mingw-org-wsl/ci/legacy/tree/mingwrt/mingwex/dirname.c) (for dirname implementation on Windows) [MIT] +- [NetBSD](https://www.netbsd.org/about/redistribution.html) (for setjmp, longjmp, and strptime implementations on Windows) [BSD-3] +- [Python](https://docs.python.org/3/license.html) (for strtod and joinpath implementation on Windows) [BSD-3, effectively] +- [Google Benchmark](https://github.com/google/benchmark) (for cyclecount implementation) [Apache 2.0] + +The following components included in Julia `Base` have their own separate licenses: + +- base/ryu/* [Boost] (see [ryu](https://github.com/ulfjack/ryu/blob/master/LICENSE-Boost)) +- base/grisu/* [BSD-3] (see [double-conversion](https://github.com/google/double-conversion/blob/master/LICENSE)) +- base/special/{exp,rem_pio2,hyperbolic}.jl [Freely distributable with preserved copyright notice] (see [FDLIBM](https://www.netlib.org/fdlibm)) + +The Julia language links to the following external libraries, which have their +own licenses: + +- [FEMTOLISP](https://github.com/JeffBezanson/femtolisp) [BSD-3] +- [LIBUNWIND](https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=blob_plain;f=LICENSE;hb=master) [MIT] +- [LIBUV](https://github.com/joyent/libuv/blob/master/LICENSE) [MIT] +- [LLVM](https://releases.llvm.org/6.0.0/LICENSE.TXT) [BSD-3, effectively] +- [UTF8PROC](https://github.com/JuliaStrings/utf8proc) [MIT] + +Julia's `stdlib` uses the following external libraries, which have their own licenses: + +- [DSFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/LICENSE.txt) [BSD-3] +- [OPENLIBM](https://github.com/JuliaMath/openlibm/blob/master/LICENSE.md) [MIT, BSD-2, ISC] +- [GMP](https://gmplib.org/manual/Copying.html#Copying) [LGPL3+ or GPL2+] +- [LIBGIT2](https://github.com/libgit2/libgit2/blob/development/COPYING) [GPL2+ with unlimited linking exception] +- [CURL](https://curl.haxx.se/docs/copyright.html) [MIT/X derivative] +- [LIBSSH2](https://github.com/libssh2/libssh2/blob/master/COPYING) [BSD-3] +- [MBEDTLS](https://tls.mbed.org/how-to-get) [either GPLv2 or Apache 2.0] +- [MPFR](https://www.mpfr.org/mpfr-current/mpfr.html#Copying) [LGPL3+] +- [OPENBLAS](https://raw.github.com/xianyi/OpenBLAS/master/LICENSE) [BSD-3] +- [LAPACK](https://netlib.org/lapack/LICENSE.txt) [BSD-3] +- [PCRE](https://www.pcre.org/licence.txt) [BSD-3] +- [SUITESPARSE](http://suitesparse.com) [mix of LGPL2+ and GPL2+; see individual module licenses] + +Julia's build process uses the following external tools: + +- [PATCHELF](https://nixos.org/patchelf.html) +- [OBJCONV](https://www.agner.org/optimize/#objconv) + +Julia bundles the following external programs and libraries: + +- [7-Zip](https://www.7-zip.org/license.txt) +- [ZLIB](https://zlib.net/zlib_license.html) + +On some platforms, distributions of Julia contain SSL certificate authority certificates, +released under the [Mozilla Public License](https://en.wikipedia.org/wiki/Mozilla_Public_License). diff --git a/base/Base.jl b/base/Base.jl index 42a506479326b..09fdcb1689de3 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -107,6 +107,9 @@ include("options.jl") include("promotion.jl") include("tuple.jl") include("expr.jl") +Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} = (@_inline_meta; Pair{A, B}(convert(A, a)::A, convert(B, b)::B)) +#Pair{Any, B}(@nospecialize(a::Any), b) where {B} = (@_inline_meta; Pair{Any, B}(a, Base.convert(B, b)::B)) +#Pair{A, Any}(a, @nospecialize(b::Any)) where {A} = (@_inline_meta; Pair{A, Any}(Base.convert(A, a)::A, b)) include("pair.jl") include("traits.jl") include("range.jl") diff --git a/base/array.jl b/base/array.jl index dedc717b056d3..d629064777ce0 100644 --- a/base/array.jl +++ b/base/array.jl @@ -698,8 +698,10 @@ else end end -_array_for(::Type{T}, itr, ::HasLength) where {T} = Vector{T}(undef, Int(length(itr)::Integer)) -_array_for(::Type{T}, itr, ::HasShape{N}) where {T,N} = similar(Array{T,N}, axes(itr)) +_array_for(::Type{T}, itr, isz::HasLength) where {T} = _array_for(T, itr, isz, length(itr)) +_array_for(::Type{T}, itr, isz::HasShape{N}) where {T,N} = _array_for(T, itr, isz, axes(itr)) +_array_for(::Type{T}, itr, ::HasLength, len) where {T} = Vector{T}(undef, len) +_array_for(::Type{T}, itr, ::HasShape{N}, axs) where {T,N} = similar(Array{T,N}, axs) function collect(itr::Generator) isz = IteratorSize(itr.iter) @@ -707,12 +709,14 @@ function collect(itr::Generator) if isa(isz, SizeUnknown) return grow_to!(Vector{et}(), itr) else + shape = isz isa HasLength ? length(itr) : axes(itr) y = iterate(itr) if y === nothing return _array_for(et, itr.iter, isz) end v1, st = y - collect_to_with_first!(_array_for(typeof(v1), itr.iter, isz), v1, itr, st) + arr = _array_for(typeof(v1), itr.iter, isz, shape) + return collect_to_with_first!(arr, v1, itr, st) end end diff --git a/base/atomics.jl b/base/atomics.jl index 97405d88fd408..e6d62c3fc807b 100644 --- a/base/atomics.jl +++ b/base/atomics.jl @@ -356,13 +356,13 @@ for typ in atomictypes rt = "$lt, $lt*" irt = "$ilt, $ilt*" @eval getindex(x::Atomic{$typ}) = - llvmcall($""" + GC.@preserve x llvmcall($""" %ptr = inttoptr i$WORD_SIZE %0 to $lt* %rv = load atomic $rt %ptr acquire, align $(gc_alignment(typ)) ret $lt %rv """, $typ, Tuple{Ptr{$typ}}, unsafe_convert(Ptr{$typ}, x)) @eval setindex!(x::Atomic{$typ}, v::$typ) = - llvmcall($""" + GC.@preserve x llvmcall($""" %ptr = inttoptr i$WORD_SIZE %0 to $lt* store atomic $lt %1, $lt* %ptr release, align $(gc_alignment(typ)) ret void @@ -371,7 +371,7 @@ for typ in atomictypes # Note: atomic_cas! succeeded (i.e. it stored "new") if and only if the result is "cmp" if typ <: Integer @eval atomic_cas!(x::Atomic{$typ}, cmp::$typ, new::$typ) = - llvmcall($""" + GC.@preserve x llvmcall($""" %ptr = inttoptr i$WORD_SIZE %0 to $lt* %rs = cmpxchg $lt* %ptr, $lt %1, $lt %2 acq_rel acquire %rv = extractvalue { $lt, i1 } %rs, 0 @@ -380,7 +380,7 @@ for typ in atomictypes unsafe_convert(Ptr{$typ}, x), cmp, new) else @eval atomic_cas!(x::Atomic{$typ}, cmp::$typ, new::$typ) = - llvmcall($""" + GC.@preserve x llvmcall($""" %iptr = inttoptr i$WORD_SIZE %0 to $ilt* %icmp = bitcast $lt %1 to $ilt %inew = bitcast $lt %2 to $ilt @@ -403,7 +403,7 @@ for typ in atomictypes if rmwop in arithmetic_ops && !(typ <: ArithmeticTypes) continue end if typ <: Integer @eval $fn(x::Atomic{$typ}, v::$typ) = - llvmcall($""" + GC.@preserve x llvmcall($""" %ptr = inttoptr i$WORD_SIZE %0 to $lt* %rv = atomicrmw $rmw $lt* %ptr, $lt %1 acq_rel ret $lt %rv @@ -411,7 +411,7 @@ for typ in atomictypes else rmwop === :xchg || continue @eval $fn(x::Atomic{$typ}, v::$typ) = - llvmcall($""" + GC.@preserve x llvmcall($""" %iptr = inttoptr i$WORD_SIZE %0 to $ilt* %ival = bitcast $lt %1 to $ilt %irv = atomicrmw $rmw $ilt* %iptr, $ilt %ival acq_rel diff --git a/base/boot.jl b/base/boot.jl index dcf62a0b9cab2..32ededb12be8d 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -171,7 +171,7 @@ export # key types Any, DataType, Vararg, NTuple, Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid, - AbstractArray, DenseArray, NamedTuple, + AbstractArray, DenseArray, NamedTuple, Pair, # special objects Function, Method, Module, Symbol, Task, Array, UndefInitializer, undef, WeakRef, VecElement, @@ -813,4 +813,16 @@ _parse = nothing # support for deprecated uses of internal _apply function _apply(x...) = Core._apply_iterate(Main.Base.iterate, x...) +struct Pair{A, B} + first::A + second::B + # if we didn't inline this, it's probably because the callsite was actually dynamic + # to avoid potentially compiling many copies of this, we mark the arguments with `@nospecialize` + # but also mark the whole function with `@inline` to ensure we will inline it whenever possible + # (even if `convert(::Type{A}, a::A)` for some reason was expensive) + Pair(a, b) = new{typeof(a), typeof(b)}(a, b) + Pair{A, B}(a::A, b::B) where {A, B} = new(a, b) + Pair{Any, Any}(@nospecialize(a::Any), @nospecialize(b::Any)) = new(a, b) +end + ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 0de1f45460be0..a057a1879412c 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -35,73 +35,15 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), add_remark!(interp, sv, "Skipped call in throw block") return CallMeta(Any, false) end - valid_worlds = WorldRange() - # NOTE this is valid as far as any "constant" lattice element doesn't represent `Union` type - splitunions = 1 < unionsplitcost(argtypes) <= InferenceParams(interp).MAX_UNION_SPLITTING - mts = Core.MethodTable[] - fullmatch = Bool[] - if splitunions - split_argtypes = switchtupleunion(argtypes) - applicable = Any[] - applicable_argtypes = Vector{Any}[] # arrays like `argtypes`, including constants, for each match - infos = MethodMatchInfo[] - for arg_n in split_argtypes - sig_n = argtypes_to_type(arg_n) - mt = ccall(:jl_method_table_for, Any, (Any,), sig_n) - if mt === nothing - add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, false) - end - mt = mt::Core.MethodTable - matches = findall(sig_n, method_table(interp); limit=max_methods) - if matches === missing - add_remark!(interp, sv, "For one of the union split cases, too many methods matched") - return CallMeta(Any, false) - end - push!(infos, MethodMatchInfo(matches)) - for m in matches - push!(applicable, m) - push!(applicable_argtypes, arg_n) - end - valid_worlds = intersect(valid_worlds, matches.valid_worlds) - thisfullmatch = _any(match->(match::MethodMatch).fully_covers, matches) - found = false - for (i, mt′) in enumerate(mts) - if mt′ === mt - fullmatch[i] &= thisfullmatch - found = true - break - end - end - if !found - push!(mts, mt) - push!(fullmatch, thisfullmatch) - end - end - info = UnionSplitInfo(infos) - else - mt = ccall(:jl_method_table_for, Any, (Any,), atype) - if mt === nothing - add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, false) - end - mt = mt::Core.MethodTable - matches = findall(atype, method_table(interp, sv); limit=max_methods) - if matches === missing - # this means too many methods matched - # (assume this will always be true, so we don't compute / update valid age in this case) - add_remark!(interp, sv, "Too many methods matched") - return CallMeta(Any, false) - end - push!(mts, mt) - push!(fullmatch, _any(match->(match::MethodMatch).fully_covers, matches)) - info = MethodMatchInfo(matches) - applicable = matches.matches - valid_worlds = matches.valid_worlds - applicable_argtypes = nothing + + matches = find_matching_methods(argtypes, atype, method_table(interp, sv), InferenceParams(interp).MAX_UNION_SPLITTING, max_methods) + if isa(matches, FailedMethodMatch) + add_remark!(interp, sv, matches.reason) + return CallMeta(Any, false) end + + (; valid_worlds, applicable, info) = matches update_valid_age!(sv, valid_worlds) - applicable = applicable::Array{Any,1} napplicable = length(applicable) rettype = Bottom edges = MethodInstance[] @@ -142,10 +84,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if edge !== nothing push!(edges, edge) end - this_argtypes = applicable_argtypes === nothing ? argtypes : applicable_argtypes[i] - const_rt, const_result = abstract_call_method_with_const_args(interp, result, f, this_argtypes, match, sv, false) - if const_rt !== rt && const_rt ⊑ rt - rt = const_rt + this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] + const_result = abstract_call_method_with_const_args(interp, result, f, this_argtypes, match, sv, false) + if const_result !== nothing + const_rt, const_result = const_result + if const_rt !== rt && const_rt ⊑ rt + rt = const_rt + end end push!(const_results, const_result) if const_result !== nothing @@ -164,10 +109,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end # try constant propagation with argtypes for this match # this is in preparation for inlining, or improving the return result - this_argtypes = applicable_argtypes === nothing ? argtypes : applicable_argtypes[i] - const_this_rt, const_result = abstract_call_method_with_const_args(interp, result, f, this_argtypes, match, sv, false) - if const_this_rt !== this_rt && const_this_rt ⊑ this_rt - this_rt = const_this_rt + this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] + const_result = abstract_call_method_with_const_args(interp, result, f, this_argtypes, match, sv, false) + if const_result !== nothing + const_this_rt, const_result = const_result + if const_this_rt !== this_rt && const_this_rt ⊑ this_rt + this_rt = const_this_rt + end end push!(const_results, const_result) if const_result !== nothing @@ -211,10 +159,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), info = ConstCallInfo(info, const_results) end - if rettype isa LimitedAccuracy - union!(sv.pclimitations, rettype.causes) - rettype = rettype.typ - end + rettype = collect_limitations!(rettype, sv) # if we have argument refinement information, apply that now to get the result if is_lattice_bool(rettype) && conditionals !== nothing && fargs !== nothing slot = 0 @@ -275,7 +220,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # and avoid keeping track of a more complex result type. rettype = Any end - add_call_backedges!(interp, rettype, edges, fullmatch, mts, atype, sv) + add_call_backedges!(interp, rettype, edges, matches, atype, sv) if !isempty(sv.pclimitations) # remove self, if present delete!(sv.pclimitations, sv) for caller in sv.callers_in_cycle @@ -286,24 +231,110 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), return CallMeta(rettype, info) end -function add_call_backedges!(interp::AbstractInterpreter, - @nospecialize(rettype), - edges::Vector{MethodInstance}, - fullmatch::Vector{Bool}, mts::Vector{Core.MethodTable}, @nospecialize(atype), - sv::InferenceState) - if rettype === Any - # for `NativeInterpreter`, we don't add backedges when a new method couldn't refine - # (widen) this type - return +struct FailedMethodMatch + reason::String +end + +struct MethodMatches + applicable::Vector{Any} + info::MethodMatchInfo + valid_worlds::WorldRange + mt::Core.MethodTable + fullmatch::Bool +end + +struct UnionSplitMethodMatches + applicable::Vector{Any} + applicable_argtypes::Vector{Vector{Any}} + info::UnionSplitInfo + valid_worlds::WorldRange + mts::Vector{Core.MethodTable} + fullmatches::Vector{Bool} +end + +function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), method_table::MethodTableView, + union_split::Int, max_methods::Int) + # NOTE this is valid as far as any "constant" lattice element doesn't represent `Union` type + if 1 < unionsplitcost(argtypes) <= union_split + split_argtypes = switchtupleunion(argtypes) + infos = MethodMatchInfo[] + applicable = Any[] + applicable_argtypes = Vector{Any}[] # arrays like `argtypes`, including constants, for each match + valid_worlds = WorldRange() + mts = Core.MethodTable[] + fullmatches = Bool[] + for i in 1:length(split_argtypes) + arg_n = split_argtypes[i]::Vector{Any} + sig_n = argtypes_to_type(arg_n) + mt = ccall(:jl_method_table_for, Any, (Any,), sig_n) + mt === nothing && return FailedMethodMatch("Could not identify method table for call") + mt = mt::Core.MethodTable + matches = findall(sig_n, method_table; limit = max_methods) + if matches === missing + return FailedMethodMatch("For one of the union split cases, too many methods matched") + end + push!(infos, MethodMatchInfo(matches)) + for m in matches + push!(applicable, m) + push!(applicable_argtypes, arg_n) + end + valid_worlds = intersect(valid_worlds, matches.valid_worlds) + thisfullmatch = _any(match->(match::MethodMatch).fully_covers, matches) + found = false + for (i, mt′) in enumerate(mts) + if mt′ === mt + fullmatches[i] &= thisfullmatch + found = true + break + end + end + if !found + push!(mts, mt) + push!(fullmatches, thisfullmatch) + end + end + return UnionSplitMethodMatches(applicable, + applicable_argtypes, + UnionSplitInfo(infos), + valid_worlds, + mts, + fullmatches) + else + mt = ccall(:jl_method_table_for, Any, (Any,), atype) + if mt === nothing + return FailedMethodMatch("Could not identify method table for call") + end + mt = mt::Core.MethodTable + matches = findall(atype, method_table; limit = max_methods) + if matches === missing + # this means too many methods matched + # (assume this will always be true, so we don't compute / update valid age in this case) + return FailedMethodMatch("Too many methods matched") + end + fullmatch = _any(match->(match::MethodMatch).fully_covers, matches) + return MethodMatches(matches.matches, + MethodMatchInfo(matches), + matches.valid_worlds, + mt, + fullmatch) end +end + +function add_call_backedges!(interp::AbstractInterpreter, @nospecialize(rettype), edges::Vector{MethodInstance}, + matches::Union{MethodMatches,UnionSplitMethodMatches}, @nospecialize(atype), + sv::InferenceState) + # for `NativeInterpreter`, we don't add backedges when a new method couldn't refine (widen) this type + rettype === Any && return for edge in edges add_backedge!(edge, sv) end - for (thisfullmatch, mt) in zip(fullmatch, mts) - if !thisfullmatch - # also need an edge to the method table in case something gets - # added that did not intersect with any existing method - add_mt_backedge!(mt, atype, sv) + # also need an edge to the method table in case something gets + # added that did not intersect with any existing method + if isa(matches, MethodMatches) + matches.fullmatch || add_mt_backedge!(matches.mt, atype, sv) + else + for (thisfullmatch, mt) in zip(matches.fullmatches, matches.mts) + thisfullmatch || add_mt_backedge!(mt, atype, sv) end end end @@ -495,33 +526,39 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul @nospecialize(f), argtypes::Vector{Any}, match::MethodMatch, sv::InferenceState, va_override::Bool) mi = maybe_get_const_prop_profitable(interp, result, f, argtypes, match, sv) - mi === nothing && return Any, nothing + mi === nothing && return nothing # try constant prop' inf_cache = get_inference_cache(interp) inf_result = cache_lookup(mi, argtypes, inf_cache) if inf_result === nothing # if there might be a cycle, check to make sure we don't end up # calling ourselves here. - if result.edgecycle && _any(InfStackUnwind(sv)) do infstate - # if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`) - # we can relax the cycle detection by comparing `MethodInstance`s and allow inference to - # propagate different constant elements if the recursion is finite over the lattice - return (result.edgelimited ? match.method === infstate.linfo.def : mi === infstate.linfo) && - any(infstate.result.overridden_by_const) + let result = result # prevent capturing + if result.edgecycle && _any(InfStackUnwind(sv)) do infstate + # if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`) + # we can relax the cycle detection by comparing `MethodInstance`s and allow inference to + # propagate different constant elements if the recursion is finite over the lattice + return (result.edgelimited ? match.method === infstate.linfo.def : mi === infstate.linfo) && + any(infstate.result.overridden_by_const) + end + add_remark!(interp, sv, "[constprop] Edge cycle encountered") + return nothing end - add_remark!(interp, sv, "[constprop] Edge cycle encountered") - return Any, nothing end inf_result = InferenceResult(mi, argtypes, va_override) + if !any(inf_result.overridden_by_const) + add_remark!(interp, sv, "[constprop] Could not handle constant info in matching_cache_argtypes") + return nothing + end frame = InferenceState(inf_result, #=cache=#false, interp) - frame === nothing && return Any, nothing # this is probably a bad generated function (unsound), but just ignore it + frame === nothing && return nothing # this is probably a bad generated function (unsound), but just ignore it frame.parent = sv push!(inf_cache, inf_result) - typeinf(interp, frame) || return Any, nothing + typeinf(interp, frame) || return nothing end result = inf_result.result # if constant inference hits a cycle, just bail out - isa(result, InferenceState) && return Any, nothing + isa(result, InferenceState) && return nothing add_backedge!(mi, sv) return result, inf_result end @@ -796,9 +833,11 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n return ret, AbstractIterationInfo(calls) end if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).MAX_TUPLE_SPLAT + stateordonet = stateordonet_widened break end if !isa(stateordonet_widened, DataType) || !(stateordonet_widened <: Tuple) || isvatuple(stateordonet_widened) || length(stateordonet_widened.parameters) != 2 + stateordonet = stateordonet_widened break end nstatetype = getfield_tfunc(stateordonet, Const(2)) @@ -816,27 +855,40 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end # From here on, we start asking for results on the widened types, rather than # the precise (potentially const) state type - statetype = widenconst(statetype) - valtype = widenconst(valtype) + # statetype and valtype are reinitialized in the first iteration below from the + # (widened) stateordonet, which has not yet been fully analyzed in the loop above + statetype = Bottom + valtype = Bottom + may_have_terminated = Nothing <: stateordonet while valtype !== Any - stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], sv).rt - stateordonet = widenconst(stateordonet) - nounion = typesubtract(stateordonet, Nothing, 0) - if !isa(nounion, DataType) || !(nounion <: Tuple) || isvatuple(nounion) || length(nounion.parameters) != 2 + nounion = typeintersect(stateordonet, Tuple{Any,Any}) + if nounion !== Union{} && !isa(nounion, DataType) + # nounion is of a type we cannot handle valtype = Any break end - if nounion.parameters[1] <: valtype && nounion.parameters[2] <: statetype + if nounion === Union{} || (nounion.parameters[1] <: valtype && nounion.parameters[2] <: statetype) + # reached a fixpoint or iterator failed/gave invalid answer if typeintersect(stateordonet, Nothing) === Union{} - # Reached a fixpoint, but Nothing is not possible => iterator is infinite or failing - return Any[Bottom], nothing + # ... but cannot terminate + if !may_have_terminated + # ... and cannot have terminated prior to this loop + return Any[Bottom], nothing + else + # iterator may have terminated prior to this loop, but not during it + valtype = Bottom + end end break end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) + stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], sv).rt + stateordonet = widenconst(stateordonet) + end + if valtype !== Union{} + push!(ret, Vararg{valtype}) end - push!(ret, Vararg{valtype}) return ret, nothing end @@ -1138,7 +1190,8 @@ function abstract_invoke(interp::AbstractInterpreter, argtypes::Vector{Any}, sv: 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 - types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types) + ft = ft::DataType + types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} result = findsup(types, method_table(interp)) @@ -1160,12 +1213,14 @@ function abstract_invoke(interp::AbstractInterpreter, argtypes::Vector{Any}, sv: # t, a = ti.parameters[i], argtypes′[i] # argtypes′[i] = t ⊑ a ? t : a # end - const_rt, const_result = abstract_call_method_with_const_args(interp, result, argtype_to_function(ft′), argtypes′, match, sv, false) - if const_rt !== rt && const_rt ⊑ rt - return CallMeta(const_rt, InvokeCallInfo(match, const_result)) - else - return CallMeta(rt, InvokeCallInfo(match, nothing)) + const_result = abstract_call_method_with_const_args(interp, result, argtype_to_function(ft′), argtypes′, match, sv, false) + if const_result !== nothing + const_rt, const_result = const_result + if const_rt !== rt && const_rt ⊑ rt + return CallMeta(collect_limitations!(const_rt, sv), InvokeCallInfo(match, const_result)) + end end + return CallMeta(collect_limitations!(rt, sv), InvokeCallInfo(match, nothing)) end # call where the function is known exactly @@ -1181,6 +1236,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_apply(interp, argtypes, sv, max_methods) elseif f === invoke return abstract_invoke(interp, argtypes, sv) + elseif f === modifyfield! + return abstract_modifyfield!(interp, argtypes, sv) end return CallMeta(abstract_call_builtin(interp, f, fargs, argtypes, sv, max_methods), false) elseif f === Core.kwfunc @@ -1265,29 +1322,30 @@ end function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, argtypes::Vector{Any}, sv::InferenceState) pushfirst!(argtypes, closure.env) sig = argtypes_to_type(argtypes) - (; rt, edge) = result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, sv) + (; rt, edge) = result = abstract_call_method(interp, closure.source, sig, Core.svec(), false, sv) edge !== nothing && add_backedge!(edge, sv) tt = closure.typ - sigT = unwrap_unionall(tt).parameters[1] - match = MethodMatch(sig, Core.svec(), closure.source::Method, sig <: rewrap_unionall(sigT, tt)) + sigT = (unwrap_unionall(tt)::DataType).parameters[1] + match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) info = OpaqueClosureCallInfo(match) if !result.edgecycle - const_rettype, const_result = abstract_call_method_with_const_args(interp, result, closure, argtypes, + const_result = abstract_call_method_with_const_args(interp, result, closure, argtypes, match, sv, closure.isva) - if const_rettype ⊑ rt - rt = const_rettype - end if const_result !== nothing + const_rettype, const_result = const_result + if const_rettype ⊑ rt + rt = const_rettype + end info = ConstCallInfo(info, Union{Nothing,InferenceResult}[const_result]) end end - return CallMeta(rt, info) + return CallMeta(collect_limitations!(rt, sv), info) end function most_general_argtypes(closure::PartialOpaque) ret = Any[] cc = widenconst(closure) - argt = unwrap_unionall(cc).parameters[1] + argt = (unwrap_unionall(cc)::DataType).parameters[1] if !isa(argt, DataType) || argt.name !== typename(Tuple) argt = Tuple end @@ -1302,8 +1360,8 @@ function abstract_call(interp::AbstractInterpreter, fargs::Union{Nothing,Vector{ f = argtype_to_function(ft) if isa(ft, PartialOpaque) return abstract_call_opaque_closure(interp, ft, argtypes[2:end], sv) - elseif isa(unwrap_unionall(ft), DataType) && unwrap_unionall(ft).name === typename(Core.OpaqueClosure) - return CallMeta(rewrap_unionall(unwrap_unionall(ft).parameters[2], ft), false) + elseif (uft = unwrap_unionall(ft); isa(uft, DataType) && uft.name === typename(Core.OpaqueClosure)) + return CallMeta(rewrap_unionall((uft::DataType).parameters[2], ft), false) elseif f === nothing # non-constant function, but the number of arguments is known # and the ft is not a Builtin or IntrinsicFunction @@ -1446,7 +1504,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), return abstract_eval_special_value(interp, e, vtypes, sv) end e = e::Expr - if e.head === :call + ehead = e.head + if ehead === :call ea = e.args argtypes = collect_argtypes(interp, ea, vtypes, sv) if argtypes === nothing @@ -1456,7 +1515,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), sv.stmt_info[sv.currpc] = callinfo.info t = callinfo.rt end - elseif e.head === :new + elseif ehead === :new t = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))[1] if isconcretetype(t) && !ismutabletype(t) args = Vector{Any}(undef, length(e.args)-1) @@ -1464,7 +1523,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), anyconst = false allconst = true for i = 2:length(e.args) - at = abstract_eval_value(interp, e.args[i], vtypes, sv) + at = widenconditional(abstract_eval_value(interp, e.args[i], vtypes, sv)) if !anyconst anyconst = has_nontrivial_const_info(at) end @@ -1493,20 +1552,20 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end end end - elseif e.head === :splatnew + elseif ehead === :splatnew t = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))[1] if length(e.args) == 2 && isconcretetype(t) && !ismutabletype(t) at = abstract_eval_value(interp, e.args[2], vtypes, sv) n = fieldcount(t) - if isa(at, Const) && isa(at.val, Tuple) && n == length(at.val) && - let t = t; _all(i->getfield(at.val, i) isa fieldtype(t, i), 1:n); end + if isa(at, Const) && isa(at.val, Tuple) && n == length(at.val::Tuple) && + let t = t; _all(i->getfield(at.val::Tuple, i) isa fieldtype(t, i), 1:n); end t = Const(ccall(:jl_new_structt, Any, (Any, Any), t, at.val)) - elseif isa(at, PartialStruct) && at ⊑ Tuple && n == length(at.fields) && - let t = t, at = at; _all(i->at.fields[i] ⊑ fieldtype(t, i), 1:n); end - t = PartialStruct(t, at.fields) + elseif isa(at, PartialStruct) && at ⊑ Tuple && n == length(at.fields::Vector{Any}) && + let t = t, at = at; _all(i->(at.fields::Vector{Any})[i] ⊑ fieldtype(t, i), 1:n); end + t = PartialStruct(t, at.fields::Vector{Any}) end end - elseif e.head === :new_opaque_closure + elseif ehead === :new_opaque_closure t = Union{} if length(e.args) >= 5 ea = e.args @@ -1525,7 +1584,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end end end - elseif e.head === :foreigncall + elseif ehead === :foreigncall abstract_eval_value(interp, e.args[1], vtypes, sv) t = sp_type_rewrap(e.args[2], sv.linfo, true) for i = 3:length(e.args) @@ -1533,25 +1592,25 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = Bottom end end - elseif e.head === :cfunction + elseif ehead === :cfunction t = e.args[1] isa(t, Type) || (t = Any) abstract_eval_cfunction(interp, e, vtypes, sv) - elseif e.head === :method + elseif ehead === :method t = (length(e.args) == 1) ? Any : Nothing - elseif e.head === :copyast + elseif ehead === :copyast t = abstract_eval_value(interp, e.args[1], vtypes, sv) if t isa Const && t.val isa Expr # `copyast` makes copies of Exprs t = Expr end - elseif e.head === :invoke + elseif ehead === :invoke || ehead === :invoke_modify error("type inference data-flow error: tried to double infer a function") - elseif e.head === :isdefined + elseif ehead === :isdefined sym = e.args[1] t = Bool if isa(sym, SlotNumber) - vtyp = vtypes[slot_id(sym)] + vtyp = vtypes[slot_id(sym)]::VarState if vtyp.typ === Bottom t = Const(false) # never assigned previously elseif !vtyp.undef @@ -1566,7 +1625,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = Const(true) end elseif isa(sym, Expr) && sym.head === :static_parameter - n = sym.args[1] + n = sym.args[1]::Int if 1 <= n <= length(sv.sptypes) spty = sv.sptypes[n] if isa(spty, Const) @@ -1601,7 +1660,7 @@ function abstract_eval_global(M::Module, s::Symbol) end function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) - typ = src.ssavaluetypes[s.id] + typ = (src.ssavaluetypes::Vector{Any})[s.id] if typ === NOT_FOUND return Bottom end @@ -1689,20 +1748,19 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) isva = isa(def, Method) && def.isva nslots = nargs - isva slottypes = frame.slottypes + ssavaluetypes = frame.src.ssavaluetypes::Vector{Any} while frame.pc´´ <= n # make progress on the active ip set - local pc::Int = frame.pc´´ # current program-counter + local pc::Int = frame.pc´´ while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 #print(pc,": ",s[pc],"\n") local pc´::Int = pc + 1 # next program-counter (after executing instruction) if pc == frame.pc´´ - # need to update pc´´ to point at the new lowest instruction in W - min_pc = _bits_findnext(W.bits, pc + 1) - frame.pc´´ = min_pc == -1 ? n + 1 : min_pc + # want to update pc´´ to point at the new lowest instruction in W + frame.pc´´ = pc´ end delete!(W, pc) frame.currpc = pc - frame.cur_hand = frame.handler_at[pc] edges = frame.stmt_edges[pc] edges === nothing || empty!(edges) frame.stmt_info[pc] = nothing @@ -1744,7 +1802,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) pc´ = l else # general case - frame.handler_at[l] = frame.cur_hand changes_else = changes if isa(condt, Conditional) changes_else = conditional_changes(changes_else, condt.elsetype, condt.var) @@ -1792,7 +1849,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) for (caller, caller_pc) in frame.cycle_backedges # notify backedges of updated type information typeassert(caller.stmt_types[caller_pc], VarTable) # we must have visited this statement before - if !(caller.src.ssavaluetypes[caller_pc] === Any) + if !((caller.src.ssavaluetypes::Vector{Any})[caller_pc] === Any) # no reason to revisit if that call-site doesn't affect the final result if caller_pc < caller.pc´´ caller.pc´´ = caller_pc @@ -1802,8 +1859,8 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end end elseif hd === :enter + stmt = stmt::Expr l = stmt.args[1]::Int - frame.cur_hand = Pair{Any,Any}(l, frame.cur_hand) # propagate type info to exception handler old = states[l] newstate_catch = stupdate!(old, changes) @@ -1815,23 +1872,21 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) states[l] = newstate_catch end typeassert(states[l], VarTable) - frame.handler_at[l] = frame.cur_hand elseif hd === :leave - for i = 1:((stmt.args[1])::Int) - frame.cur_hand = (frame.cur_hand::Pair{Any,Any}).second - end else if hd === :(=) + stmt = stmt::Expr t = abstract_eval_statement(interp, stmt.args[2], changes, frame) if t === Bottom break end - frame.src.ssavaluetypes[pc] = t + ssavaluetypes[pc] = t lhs = stmt.args[1] if isa(lhs, SlotNumber) changes = StateUpdate(lhs, VarState(t, false), changes, false) end elseif hd === :method + stmt = stmt::Expr fname = stmt.args[1] if isa(fname, SlotNumber) changes = StateUpdate(fname, VarState(Any, false), changes, false) @@ -1846,19 +1901,25 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) if !isempty(frame.ssavalue_uses[pc]) record_ssa_assign(pc, t, frame) else - frame.src.ssavaluetypes[pc] = t + ssavaluetypes[pc] = t end end - if frame.cur_hand !== nothing && isa(changes, StateUpdate) - # propagate new type info to exception handler - # the handling for Expr(:enter) propagates all changes from before the try/catch - # so this only needs to propagate any changes - l = frame.cur_hand.first::Int - if stupdate1!(states[l]::VarTable, changes::StateUpdate) !== false - if l < frame.pc´´ - frame.pc´´ = l + if isa(changes, StateUpdate) + let cur_hand = frame.handler_at[pc], l, enter + while cur_hand != 0 + enter = frame.src.code[cur_hand] + l = (enter::Expr).args[1]::Int + # propagate new type info to exception handler + # the handling for Expr(:enter) propagates all changes from before the try/catch + # so this only needs to propagate any changes + if stupdate1!(states[l]::VarTable, changes::StateUpdate) !== false + if l < frame.pc´´ + frame.pc´´ = l + end + push!(W, l) + end + cur_hand = frame.handler_at[cur_hand] end - push!(W, l) end end end @@ -1867,11 +1928,10 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) if t === nothing # mark other reached expressions as `Any` to indicate they don't throw - frame.src.ssavaluetypes[pc] = Any + ssavaluetypes[pc] = Any end pc´ > n && break # can't proceed with the fast-path fall-through - frame.handler_at[pc´] = frame.cur_hand newstate = stupdate!(states[pc´], changes) if isa(stmt, GotoNode) && frame.pc´´ < pc´ # if we are processing a goto node anyways, @@ -1882,7 +1942,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) states[pc´] = newstate end push!(W, pc´) - pc = frame.pc´´ + break elseif newstate !== nothing states[pc´] = newstate pc = pc´ @@ -1892,6 +1952,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) break end end + frame.pc´´ = _bits_findnext(W.bits, frame.pc´´)::Int # next program-counter end frame.dont_work_on_me = false nothing diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 327ab85d104f3..483e2f38d9ee8 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -13,33 +13,35 @@ end # for the provided `linfo` and `given_argtypes`. The purpose of this function is # to return a valid value for `cache_lookup(linfo, argtypes, cache).argtypes`, # so that we can construct cache-correct `InferenceResult`s in the first place. -function matching_cache_argtypes(linfo::MethodInstance, given_argtypes::Vector, va_override) +function matching_cache_argtypes(linfo::MethodInstance, given_argtypes::Vector, va_override::Bool) @assert isa(linfo.def, Method) # ensure the next line works nargs::Int = linfo.def.nargs - @assert length(given_argtypes) >= (nargs - 1) given_argtypes = anymap(widenconditional, given_argtypes) - if va_override || linfo.def.isva + isva = va_override || linfo.def.isva + if isva || isvarargtype(given_argtypes[end]) isva_given_argtypes = Vector{Any}(undef, nargs) - for i = 1:(nargs - 1) + for i = 1:(nargs - isva) isva_given_argtypes[i] = argtype_by_index(given_argtypes, i) end - if length(given_argtypes) >= nargs || !isvarargtype(given_argtypes[end]) - isva_given_argtypes[nargs] = tuple_tfunc(given_argtypes[nargs:end]) - else - isva_given_argtypes[nargs] = tuple_tfunc(given_argtypes[end:end]) + if isva + if length(given_argtypes) < nargs && isvarargtype(given_argtypes[end]) + last = length(given_argtypes) + else + last = nargs + end + isva_given_argtypes[nargs] = tuple_tfunc(given_argtypes[last:end]) end given_argtypes = isva_given_argtypes end + @assert length(given_argtypes) == nargs cache_argtypes, overridden_by_const = matching_cache_argtypes(linfo, nothing, va_override) - if nargs === length(given_argtypes) - for i in 1:nargs - given_argtype = given_argtypes[i] - cache_argtype = cache_argtypes[i] - if !is_argtype_match(given_argtype, cache_argtype, overridden_by_const[i]) - # prefer the argtype we were given over the one computed from `linfo` - cache_argtypes[i] = given_argtype - overridden_by_const[i] = true - end + for i in 1:nargs + given_argtype = given_argtypes[i] + cache_argtype = cache_argtypes[i] + if !is_argtype_match(given_argtype, cache_argtype, overridden_by_const[i]) + # prefer the argtype we were given over the one computed from `linfo` + cache_argtypes[i] = given_argtype + overridden_by_const[i] = true end end return cache_argtypes, overridden_by_const diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index cb5d2009a9171..f13622edb23fe 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -28,9 +28,7 @@ mutable struct InferenceState pc´´::LineNum nstmts::Int # current exception handler info - cur_hand #::Union{Nothing, Pair{LineNum, prev_handler}} - handler_at::Vector{Any} - n_handlers::Int + handler_at::Vector{LineNum} # ssavalue sparsity and restart info ssavalue_uses::Vector{BitSet} throw_blocks::BitSet @@ -87,12 +85,9 @@ mutable struct InferenceState throw_blocks = find_throw_blocks(code) # exception handlers - cur_hand = nothing - handler_at = Any[ nothing for i=1:n ] - n_handlers = 0 - - W = BitSet() - push!(W, 1) #initial pc to visit + ip = BitSet() + handler_at = compute_trycatch(src.code, ip) + push!(ip, 1) if !toplevel meth = linfo.def @@ -103,14 +98,14 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) + frame = new( InferenceParams(interp), result, linfo, sp, slottypes, inmodule, 0, IdSet{InferenceState}(), IdSet{InferenceState}(), src, get_world_counter(interp), valid_worlds, nargs, s_types, s_edges, stmt_info, - Union{}, W, 1, n, - cur_hand, handler_at, n_handlers, + Union{}, ip, 1, n, handler_at, ssavalue_uses, throw_blocks, Vector{Tuple{InferenceState,LineNum}}(), # cycle_backedges Vector{InferenceState}(), # callers_in_cycle @@ -124,6 +119,91 @@ mutable struct InferenceState end end +function compute_trycatch(code::Vector{Any}, ip::BitSet) + # The goal initially is to record the frame like this for the state at exit: + # 1: (enter 3) # == 0 + # 3: (expr) # == 1 + # 3: (leave 1) # == 1 + # 4: (expr) # == 0 + # then we can find all trys by walking backwards from :enter statements, + # and all catches by looking at the statement after the :enter + n = length(code) + empty!(ip) + ip.offset = 0 # for _bits_findnext + push!(ip, n + 1) + handler_at = fill(0, n) + + # start from all :enter statements and record the location of the try + for pc = 1:n + stmt = code[pc] + if isexpr(stmt, :enter) + l = stmt.args[1]::Int + handler_at[pc + 1] = pc + push!(ip, pc + 1) + handler_at[l] = pc + push!(ip, l) + end + end + + # now forward those marks to all :leave statements + pc´´ = 0 + while true + # make progress on the active ip set + pc = _bits_findnext(ip.bits, pc´´)::Int + pc > n && break + while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 + pc´ = pc + 1 # next program-counter (after executing instruction) + if pc == pc´´ + pc´´ = pc´ + end + delete!(ip, pc) + cur_hand = handler_at[pc] + @assert cur_hand != 0 "unbalanced try/catch" + stmt = code[pc] + if isa(stmt, GotoNode) + pc´ = stmt.label + elseif isa(stmt, GotoIfNot) + l = stmt.dest::Int + if handler_at[l] != cur_hand + @assert handler_at[l] == 0 "unbalanced try/catch" + handler_at[l] = cur_hand + if l < pc´´ + pc´´ = l + end + push!(ip, l) + end + elseif isa(stmt, ReturnNode) + @assert !isdefined(stmt, :val) "unbalanced try/catch" + break + elseif isa(stmt, Expr) + head = stmt.head + if head === :enter + cur_hand = pc + elseif head === :leave + l = stmt.args[1]::Int + for i = 1:l + cur_hand = handler_at[cur_hand] + end + cur_hand == 0 && break + end + end + + pc´ > n && break # can't proceed with the fast-path fall-through + if handler_at[pc´] != cur_hand + @assert handler_at[pc´] == 0 "unbalanced try/catch" + handler_at[pc´] = cur_hand + elseif !in(pc´, ip) + break # already visited + end + pc = pc´ + end + end + + @assert first(ip) == n + 1 + return handler_at +end + + """ Iterate through all callers of the given InferenceState in the abstract interpretation stack (including the given InferenceState itself), vising @@ -185,7 +265,7 @@ function sptypes_from_meth_instance(linfo::MethodInstance) while temp isa UnionAll temp = temp.body end - sigtypes = temp.parameters + sigtypes = (temp::DataType).parameters for j = 1:length(sigtypes) tj = sigtypes[j] if isType(tj) && tj.parameters[1] === Pi diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 6d059247a43ea..b8ec9610e0739 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -196,10 +196,11 @@ function stmt_affects_purity(@nospecialize(stmt), ir) return true end -# Convert IRCode back to CodeInfo and compute inlining cost and sideeffects +# compute inlining cost and sideeffects function finish(interp::AbstractInterpreter, opt::OptimizationState, params::OptimizationParams, ir::IRCode, @nospecialize(result)) - def = opt.linfo.def - nargs = Int(opt.nargs) - 1 + (; src, nargs, linfo) = opt + (; def, specTypes) = linfo + nargs = Int(nargs) - 1 force_noinline = _any(@nospecialize(x) -> isexpr(x, :meta) && x.args[1] === :noinline, ir.meta) @@ -221,7 +222,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, params::Opt end end if proven_pure - for fl in opt.src.slotflags + for fl in src.slotflags if (fl & SLOT_USEDUNDEF) != 0 proven_pure = false break @@ -230,7 +231,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, params::Opt end end if proven_pure - opt.src.pure = true + src.pure = true end if proven_pure @@ -243,7 +244,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, params::Opt if !(isa(result, Const) && !is_inlineable_constant(result.val)) opt.const_api = true end - force_noinline || (opt.src.inlineable = true) + force_noinline || (src.inlineable = true) end end @@ -252,7 +253,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, params::Opt # determine and cache inlineability union_penalties = false if !force_noinline - sig = unwrap_unionall(opt.linfo.specTypes) + sig = unwrap_unionall(specTypes) if isa(sig, DataType) && sig.name === Tuple.name for P in sig.parameters P = unwrap_unionall(P) @@ -264,25 +265,25 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, params::Opt else force_noinline = true end - if !opt.src.inlineable && result === Union{} + if !src.inlineable && result === Union{} force_noinline = true end end if force_noinline - opt.src.inlineable = false + src.inlineable = false elseif isa(def, Method) - if opt.src.inlineable && isdispatchtuple(opt.linfo.specTypes) + if src.inlineable && isdispatchtuple(specTypes) # obey @inline declaration if a dispatch barrier would not help else bonus = 0 if result ⊑ Tuple && !isconcretetype(widenconst(result)) bonus = params.inline_tupleret_bonus end - if opt.src.inlineable + if src.inlineable # For functions declared @inline, increase the cost threshold 20x bonus += params.inline_cost_threshold*19 end - opt.src.inlineable = isinlineable(def, opt, params, union_penalties, bonus) + src.inlineable = isinlineable(def, opt, params, union_penalties, bonus) end end @@ -502,7 +503,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return 0 end return error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty - elseif head === :foreigncall || head === :invoke + elseif head === :foreigncall || head === :invoke || head == :invoke_modify # Calls whose "return type" is Union{} do not actually return: # they are errors. Since these are not part of the typical # run-time of the function, we omit them from diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 78edef88439e9..077a1f105d3d8 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -313,8 +313,10 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector push!(linetable, LineInfoNode(entry.module, entry.method, entry.file, entry.line, (entry.inlined_at > 0 ? entry.inlined_at + linetable_offset : inlined_at))) end - nargs_def = item.mi.def.nargs - isva = nargs_def > 0 && item.mi.def.isva + (; def, sparam_vals) = item.mi + nargs_def = def.nargs::Int32 + isva = nargs_def > 0 && def.isva + sig = def.sig if isva vararg = mk_tuplecall!(compact, argexprs[nargs_def:end], compact.result[idx][:line]) argexprs = Any[argexprs[1:(nargs_def - 1)]..., vararg] @@ -347,7 +349,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector # face of rename_arguments! mutating in place - should figure out # something better eventually. inline_compact[idx′] = nothing - stmt′ = ssa_substitute!(idx′, stmt′, argexprs, item.mi.def.sig, item.mi.sparam_vals, linetable_offset, boundscheck_idx, compact) + stmt′ = ssa_substitute!(idx′, stmt′, argexprs, sig, sparam_vals, linetable_offset, boundscheck_idx, compact) if isa(stmt′, ReturnNode) isa(stmt′.val, SSAValue) && (compact.used_ssas[stmt′.val.id] += 1) return_value = SSAValue(idx′) @@ -374,7 +376,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector inline_compact = IncrementalCompact(compact, spec.ir, compact.result_idx) for ((_, idx′), stmt′) in inline_compact inline_compact[idx′] = nothing - stmt′ = ssa_substitute!(idx′, stmt′, argexprs, item.mi.def.sig, item.mi.sparam_vals, linetable_offset, boundscheck_idx, compact) + stmt′ = ssa_substitute!(idx′, stmt′, argexprs, sig, sparam_vals, linetable_offset, boundscheck_idx, compact) if isa(stmt′, ReturnNode) if isdefined(stmt′, :val) val = stmt′.val @@ -709,9 +711,8 @@ function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::Meth return mi end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, result::InferenceResult) - mi = specialize_method(result.linfo.def::Method, result.linfo.specTypes, - result.linfo.sparam_vals, false, true) +function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult) + mi = specialize_method(linfo.def::Method, linfo.specTypes, linfo.sparam_vals, false, true) mi !== nothing && et !== nothing && push!(et, mi::MethodInstance) return mi end @@ -1065,9 +1066,9 @@ function inline_invoke!(ir::IRCode, idx::Int, sig::Signature, (; match, result): pushfirst!(atypes, atype0) if isa(result, InferenceResult) - item = InliningTodo(result, atypes, calltype) - validate_sparams(item.mi.sparam_vals) || return nothing - if argtypes_to_type(atypes) <: item.mi.def.sig + (; mi) = item = InliningTodo(result, atypes, calltype) + validate_sparams(mi.sparam_vals) || return nothing + if argtypes_to_type(atypes) <: mi.def.sig state.mi_cache !== nothing && (item = resolve_todo(item, state)) handle_single_case!(ir, stmt, idx, item, true, todo) return nothing @@ -1148,6 +1149,22 @@ function process_simple!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int, sta ir.stmts[idx][:inst] = res return nothing end + if (sig.f === modifyfield! || sig.ft ⊑ typeof(modifyfield!)) && 5 <= length(stmt.args) <= 6 + let info = ir.stmts[idx][:info] + info isa MethodResultPure && (info = info.info) + info isa ConstCallInfo && (info = info.call) + info isa MethodMatchInfo || return nothing + length(info.results) == 1 || return nothing + match = info.results[1]::MethodMatch + match.fully_covers || return nothing + case = compileable_specialization(state.et, match) + case === nothing && return nothing + stmt.head = :invoke_modify + pushfirst!(stmt.args, case) + ir.stmts[idx][:inst] = stmt + end + return nothing + end check_effect_free!(ir, stmt, calltype, idx) @@ -1179,7 +1196,7 @@ function analyze_single_call!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int for i in 1:length(infos) info = infos[i] meth = info.results - if meth === missing || meth.ambig + if meth.ambig # Too many applicable methods # Or there is a (partial?) ambiguity too_many = true @@ -1197,8 +1214,9 @@ function analyze_single_call!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int only_method = false end for match in meth - signature_union = Union{signature_union, match.spec_types} - if !isdispatchtuple(match.spec_types) + spec_types = match.spec_types + signature_union = Union{signature_union, spec_types} + if !isdispatchtuple(spec_types) fully_covered = false continue end @@ -1206,10 +1224,10 @@ function analyze_single_call!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int if case === nothing fully_covered = false continue - elseif _any(p->p[1] === match.spec_types, cases) + elseif _any(p->p[1] === spec_types, cases) continue end - push!(cases, Pair{Any,Any}(match.spec_types, case)) + push!(cases, Pair{Any,Any}(spec_types, case)) end end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 50483ffa79465..9d4657fcf20f4 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -403,7 +403,8 @@ function getindex(x::UseRef) end function is_relevant_expr(e::Expr) - return e.head in (:call, :invoke, :new, :splatnew, :(=), :(&), + return e.head in (:call, :invoke, :invoke_modify, + :new, :splatnew, :(=), :(&), :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :undefcheck, :throw_undef_if_not, diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 49d9aef973e29..e9fddd1d12a02 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -47,7 +47,7 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs::Int) for metanode in ir.meta push!(ci.code, metanode) push!(ci.codelocs, 1) - push!(ci.ssavaluetypes, Any) + push!(ci.ssavaluetypes::Vector{Any}, Any) push!(ci.ssaflags, 0x00) end # Translate BB Edges to statement edges diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index ce20a61a4e983..7c8964d371122 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1064,7 +1064,7 @@ function type_lift_pass!(ir::IRCode) if haskey(processed, id) val = processed[id] else - push!(worklist, (id, up_id, new_phi, i)) + push!(worklist, (id, up_id, new_phi::SSAValue, i)) continue end else diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 1f1c838c62ae7..a327de623bb09 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -628,7 +628,7 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info if new_node_type === UNDEF # try to be robust against errors printstyled(io, "::#UNDEF", color=:red) elseif show_type - line_info_postprinter(io, new_node_type, node_idx in used) + line_info_postprinter(IOContext(io, :idx => node_idx), new_node_type, node_idx in used) end println(io) i += 1 @@ -643,7 +643,7 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info # This is an error, but can happen if passes don't update their type information printstyled(io, "::#UNDEF", color=:red) elseif show_type - line_info_postprinter(io, type, idx in used) + line_info_postprinter(IOContext(io, :idx => idx), type, idx in used) end end println(io) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 3b6953fc53d19..91543835c8c06 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -823,7 +823,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, defuse, narg new_dest = block_for_inst(cfg, stmt.dest) if new_dest == bb+1 # Drop this node - it's a noop - new_code[idx] = stmt.cond + new_code[idx] = Expr(:call, GlobalRef(Core, :typeassert), stmt.cond, GlobalRef(Core, :Bool)) else new_code[idx] = GotoIfNot(stmt.cond, new_dest) end @@ -871,7 +871,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, defuse, narg changed = false for new_idx in type_refine_phi node = new_nodes.stmts[new_idx] - new_typ = recompute_type(node[:inst], ci, ir, ir.sptypes, slottypes) + new_typ = recompute_type(node[:inst]::Union{PhiNode,PhiCNode}, ci, ir, ir.sptypes, slottypes) if !(node[:type] ⊑ new_typ) || !(new_typ ⊑ node[:type]) node[:type] = new_typ changed = true diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index a6ffee299c4f5..0c54e9359fa1a 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -9,7 +9,7 @@ to re-consult the method table. This info is illegal on any statement that is not a call to a generic function. """ struct MethodMatchInfo - results::Union{Missing, MethodLookupResult} + results::MethodLookupResult end """ diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index c31b32428a297..ca03710bbbd47 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -466,29 +466,51 @@ add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) add_tfunc(applicable, 1, INT_INF, (@nospecialize(f), args...)->Bool, 100) add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecialize(x)->Int, 4) add_tfunc(arraysize, 2, 2, (@nospecialize(a), @nospecialize(d))->Int, 4) + function pointer_eltype(@nospecialize(ptr)) a = widenconst(ptr) - if a <: Ptr - if isa(a, DataType) && isa(a.parameters[1], Type) - return a.parameters[1] - elseif isa(a, UnionAll) && !has_free_typevars(a) - unw = unwrap_unionall(a) - if isa(unw, DataType) - return rewrap_unionall(unw.parameters[1], a) - end + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + T isa Type && return rewrap_unionall(T, a) end end return Any end +function atomic_pointermodify_tfunc(ptr, op, v, order) + @nospecialize + a = widenconst(ptr) + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + # note: we could sometimes refine this to a PartialStruct if we analyzed `op(T, T)::T` + T isa Type && return rewrap_unionall(Pair{T, T}, a) + end + end + return Pair +end +function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) + @nospecialize + a = widenconst(ptr) + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + T isa Type && return rewrap_unionall(ccall(:jl_apply_cmpswap_type, Any, (Any,), T), a) + end + end + return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T +end add_tfunc(pointerref, 3, 3, (a, i, align) -> (@nospecialize; pointer_eltype(a)), 4) add_tfunc(pointerset, 4, 4, (a, v, i, align) -> (@nospecialize; a), 5) - add_tfunc(atomic_fence, 1, 1, (order) -> (@nospecialize; Nothing), 4) add_tfunc(atomic_pointerref, 2, 2, (a, order) -> (@nospecialize; pointer_eltype(a)), 4) add_tfunc(atomic_pointerset, 3, 3, (a, v, order) -> (@nospecialize; a), 5) add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_eltype(a)), 5) -add_tfunc(atomic_pointermodify, 4, 4, (a, op, v, order) -> (@nospecialize; T = pointer_eltype(a); Tuple{T, T}), 5) -add_tfunc(atomic_pointerreplace, 5, 5, (a, x, v, success_order, failure_order) -> (@nospecialize; Tuple{pointer_eltype(a), Bool}), 5) +add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) +add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) # more accurate typeof_tfunc for vararg tuples abstract only in length function typeof_concrete_vararg(t::DataType) @@ -911,11 +933,55 @@ setfield!_tfunc(o, f, v) = (@nospecialize; v) swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f)) swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f)) -modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{T, T}) -modifyfield!_tfunc(o, f, op, v) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{T, T}) # TODO: also model op(o.f, v) call +modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; modifyfield!_tfunc(o, f, op, v)) +function modifyfield!_tfunc(o, f, op, v) + @nospecialize + T = _fieldtype_tfunc(o, isconcretetype(o), f) + T === Bottom && return Bottom + PT = Const(Pair) + return instanceof_tfunc(apply_type_tfunc(PT, T, T))[1] +end +function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState) + nargs = length(argtypes) + if !isempty(argtypes) && isvarargtype(argtypes[nargs]) + nargs - 1 <= 6 || return CallMeta(Bottom, false) + nargs > 3 || return CallMeta(Any, false) + else + 5 <= nargs <= 6 || return CallMeta(Bottom, false) + end + o = unwrapva(argtypes[2]) + f = unwrapva(argtypes[3]) + RT = modifyfield!_tfunc(o, f, Any, Any) + info = false + if nargs >= 5 && RT !== Bottom + # we may be able to refine this to a PartialStruct by analyzing `op(o.f, v)::T` + # as well as compute the info for the method matches + op = unwrapva(argtypes[4]) + v = unwrapva(argtypes[5]) + TF = getfield_tfunc(o, f) + push!(sv.ssavalue_uses[sv.currpc], sv.currpc) # temporarily disable `call_result_unused` check for this call + callinfo = abstract_call(interp, nothing, Any[op, TF, v], sv, #=max_methods=# 1) + pop!(sv.ssavalue_uses[sv.currpc], sv.currpc) + TF2 = tmeet(callinfo.rt, widenconst(TF)) + if TF2 === Bottom + RT = Bottom + elseif isconcretetype(RT) && has_nontrivial_const_info(TF2) # isconcrete condition required to form a PartialStruct + RT = PartialStruct(RT, Any[TF, TF2]) + end + info = callinfo.info + end + return CallMeta(RT, info) +end replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) -replacefield!_tfunc(o, f, x, v) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{widenconst(T), Bool}) +function replacefield!_tfunc(o, f, x, v) + @nospecialize + T = _fieldtype_tfunc(o, isconcretetype(o), f) + T === Bottom && return Bottom + PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) + return instanceof_tfunc(apply_type_tfunc(PT, T))[1] +end + # we could use tuple_tfunc instead of widenconst, but `o` is mutable, so that is unlikely to be beneficial add_tfunc(getfield, 2, 4, getfield_tfunc, 1) @@ -1561,7 +1627,7 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp if length(argtypes) - 1 == tf[2] argtypes = argtypes[1:end-1] else - vatype = argtypes[end] + vatype = argtypes[end]::Core.TypeofVararg argtypes = argtypes[1:end-1] while length(argtypes) < tf[1] push!(argtypes, unwrapva(vatype)) @@ -1667,7 +1733,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s aft = argtypes[2] if isa(aft, Const) || (isType(aft) && !has_free_typevars(aft)) || (isconcretetype(aft) && !(aft <: Builtin)) - af_argtype = isa(tt, Const) ? tt.val : tt.parameters[1] + af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1] if isa(af_argtype, DataType) && af_argtype <: Tuple argtypes_vec = Any[aft, af_argtype.parameters...] if contains_is(argtypes_vec, Union{}) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 4ad96ae2e72f0..ef6e5a161864a 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# build (and start inferring) the inference frame for the linfo +# build (and start inferring) the inference frame for the top-level MethodInstance function typeinf(interp::AbstractInterpreter, result::InferenceResult, cached::Bool) frame = InferenceState(result, cached, interp) frame === nothing && return false @@ -243,7 +243,7 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) # collect results for the new expanded frame results = Tuple{InferenceResult, Vector{Any}, Bool}[ ( frames[i].result, - frames[i].stmt_edges[1], + frames[i].stmt_edges[1]::Vector{Any}, frames[i].cached ) for i in 1:length(frames) ] empty!(frames) @@ -341,7 +341,7 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta if cache_the_tree if may_compress(interp) nslots = length(ci.slotflags) - resize!(ci.slottypes, nslots) + resize!(ci.slottypes::Vector{Any}, nslots) resize!(ci.slotnames, nslots) return ccall(:jl_compress_ir, Any, (Any, Any), def, ci) else @@ -386,17 +386,18 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) end # check if the existing linfo metadata is also sufficient to describe the current inference result # to decide if it is worth caching this - already_inferred = already_inferred_quick_test(interp, result.linfo) - if !already_inferred && haskey(WorldView(code_cache(interp), valid_worlds), result.linfo) + linfo = result.linfo + already_inferred = already_inferred_quick_test(interp, linfo) + if !already_inferred && haskey(WorldView(code_cache(interp), valid_worlds), linfo) already_inferred = true end # 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, result.linfo, valid_worlds, result.src) - code_cache(interp)[result.linfo] = CodeInstance(result, inferred_result, valid_worlds) + inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src) + code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) end - unlock_mi_inference(interp, result.linfo) + unlock_mi_inference(interp, linfo) nothing end @@ -437,7 +438,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) empty!(edges) end if me.src.edges !== nothing - append!(s_edges, me.src.edges) + append!(s_edges, me.src.edges::Vector) me.src.edges = nothing end # inspect whether our inference had a limited result accuracy, @@ -446,7 +447,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) limited_ret = me.bestguess isa LimitedAccuracy limited_src = false if !limited_ret - gt = me.src.ssavaluetypes + gt = me.src.ssavaluetypes::Vector{Any} for j = 1:length(gt) gt[j] = gtj = cycle_fix_limited(gt[j], me) if gtj isa LimitedAccuracy && me.parent !== nothing @@ -510,8 +511,9 @@ end # widen all Const elements in type annotations function widen_all_consts!(src::CodeInfo) - for i = 1:length(src.ssavaluetypes) - src.ssavaluetypes[i] = widenconst(src.ssavaluetypes[i]) + ssavaluetypes = src.ssavaluetypes::Vector{Any} + for i = 1:length(ssavaluetypes) + ssavaluetypes[i] = widenconst(ssavaluetypes[i]) end for i = 1:length(src.code) @@ -576,6 +578,7 @@ function record_slot_assign!(sv::InferenceState) states = sv.stmt_types body = sv.src.code::Vector{Any} slottypes = sv.slottypes::Vector{Any} + ssavaluetypes = sv.src.ssavaluetypes::Vector{Any} for i = 1:length(body) expr = body[i] st_i = states[i] @@ -584,7 +587,7 @@ function record_slot_assign!(sv::InferenceState) lhs = expr.args[1] rhs = expr.args[2] if isa(lhs, SlotNumber) - vt = widenconst(sv.src.ssavaluetypes[i]) + vt = widenconst(ssavaluetypes[i]) if vt !== Bottom id = slot_id(lhs) otherTy = slottypes[id] @@ -607,12 +610,11 @@ function type_annotate!(sv::InferenceState, run_optimizer::Bool) # (otherwise, we'll perhaps run the optimization passes later, outside of inference) # remove all unused ssa values - gt = sv.src.ssavaluetypes - for j = 1:length(gt) - if gt[j] === NOT_FOUND - gt[j] = Union{} - end - gt[j] = widenconditional(gt[j]) + src = sv.src + ssavaluetypes = src.ssavaluetypes::Vector{Any} + for j = 1:length(ssavaluetypes) + t = ssavaluetypes[j] + ssavaluetypes[j] = t === NOT_FOUND ? Union{} : widenconditional(t) end # compute the required type for each slot @@ -625,7 +627,6 @@ function type_annotate!(sv::InferenceState, run_optimizer::Bool) # annotate variables load types # remove dead code optimization # and compute which variables may be used undef - src = sv.src states = sv.stmt_types nargs = sv.nargs nslots = length(states[1]::VarTable) @@ -638,7 +639,7 @@ function type_annotate!(sv::InferenceState, run_optimizer::Bool) expr = body[i] if isa(expr, GotoIfNot) if !isa(states[expr.dest], VarTable) - body[i] = expr.cond + body[i] = Expr(:call, GlobalRef(Core, :typeassert), expr.cond, GlobalRef(Core, :Bool)) end end end @@ -668,7 +669,7 @@ function type_annotate!(sv::InferenceState, run_optimizer::Bool) elseif run_optimizer deleteat!(body, i) deleteat!(states, i) - deleteat!(src.ssavaluetypes, i) + deleteat!(ssavaluetypes, i) deleteat!(src.codelocs, i) deleteat!(sv.stmt_info, i) nexpr -= 1 diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 6391d4029b58e..2f026d41efb35 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -96,8 +96,18 @@ end struct LimitedAccuracy typ causes::IdSet{InferenceState} - LimitedAccuracy(@nospecialize(typ), causes::IdSet{InferenceState}) = - new(typ, causes) + function LimitedAccuracy(@nospecialize(typ), causes::IdSet{InferenceState}) + @assert !isa(typ, LimitedAccuracy) "malformed LimitedAccuracy" + return new(typ, causes) + end +end + +@inline function collect_limitations!(@nospecialize(typ), sv::InferenceState) + if isa(typ, LimitedAccuracy) + union!(sv.pclimitations, typ.causes) + return typ.typ + end + return typ end struct NotFound end @@ -299,7 +309,7 @@ function smerge(sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}) end @inline tchanged(@nospecialize(n), @nospecialize(o)) = o === NOT_FOUND || (n !== NOT_FOUND && !(n ⊑ o)) -@inline schanged(@nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !issubstate(n, o))) +@inline schanged(@nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !issubstate(n::VarState, o::VarState))) widenconditional(@nospecialize typ) = typ function widenconditional(typ::AnyConditional) @@ -396,7 +406,7 @@ function stupdate1!(state::VarTable, change::StateUpdate) if isa(oldtypetyp, Conditional) && slot_id(oldtypetyp.var) == changeid oldtypetyp = widenconditional(oldtypetyp) if oldtype.typ isa LimitedAccuracy - oldtypetyp = LimitedAccuracy(oldtypetyp, oldtype.typ.causes) + oldtypetyp = LimitedAccuracy(oldtypetyp, (oldtype.typ::LimitedAccuracy).causes) end state[i] = VarState(oldtypetyp, oldtype.undef) end diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 3145517630958..23045c65cc6bb 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -130,7 +130,7 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec elseif isa(t, DataType) if isa(c, Core.TypeofVararg) # Tuple{Vararg{T}} --> Tuple{T} is OK - return _limit_type_size(t, c.T, sources, depth, 0) + return _limit_type_size(t, unwrapva(c), sources, depth, 0) elseif isType(t) # allow taking typeof as Type{...}, but ensure it doesn't start nesting tt = unwrap_unionall(t.parameters[1]) (!isa(tt, DataType) || isType(tt)) && (depth += 1) diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 3b84395c676d2..45c66a773815f 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -155,7 +155,15 @@ function subst_trivial_bounds(@nospecialize(atypes)) end v = atypes.var if isconcretetype(v.ub) || v.lb === v.ub - return subst_trivial_bounds(atypes{v.ub}) + subst = try + atypes{v.ub} + catch + # Note in rare cases a var bound might not be valid to substitute. + nothing + end + if subst !== nothing + return subst_trivial_bounds(subst) + end end return UnionAll(v, subst_trivial_bounds(atypes.body)) end diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index f6b89f8f5cd04..02fb1b02c6ef0 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -1,9 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # Expr head => argument count bounds -const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange}( +const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :call => 1:typemax(Int), :invoke => 2:typemax(Int), + :invoke_modify => 3:typemax(Int), :static_parameter => 1:1, :(&) => 1:1, :(=) => 2:2, @@ -76,7 +77,7 @@ end function _validate_val!(@nospecialize(x), errors, ssavals::BitSet) if isa(x, Expr) - if x.head === :call || x.head === :invoke + if x.head === :call || x.head === :invoke || x.head === :invoke_modify f = x.args[1] if f isa GlobalRef && (f.name === :cglobal) && x.head === :call # TODO: these are not yet linearized @@ -136,7 +137,8 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ end validate_val!(lhs) validate_val!(rhs) - elseif head === :call || head === :invoke || head === :gc_preserve_end || head === :meta || + elseif head === :call || head === :invoke || x.head === :invoke_modify || + head === :gc_preserve_end || head === :meta || head === :inbounds || head === :foreigncall || head === :cfunction || head === :const || head === :enter || head === :leave || head === :pop_exception || head === :method || head === :global || head === :static_parameter || @@ -180,10 +182,11 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ !is_top_level && nslotnames == 0 && push!(errors, InvalidCodeError(EMPTY_SLOTNAMES)) nslotnames < nslotflags && push!(errors, InvalidCodeError(SLOTFLAGS_MISMATCH, (nslotnames, nslotflags))) if c.inferred - nssavaluetypes = length(c.ssavaluetypes) + nssavaluetypes = length(c.ssavaluetypes::Vector{Any}) nssavaluetypes < nssavals && push!(errors, InvalidCodeError(SSAVALUETYPES_MISMATCH, (nssavals, nssavaluetypes))) else - c.ssavaluetypes != nssavals && push!(errors, InvalidCodeError(SSAVALUETYPES_MISMATCH_UNINFERRED, (nssavals, c.ssavaluetypes))) + ssavaluetypes = c.ssavaluetypes::Int + ssavaluetypes != nssavals && push!(errors, InvalidCodeError(SSAVALUETYPES_MISMATCH_UNINFERRED, (nssavals, ssavaluetypes))) end return errors end @@ -205,7 +208,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, mi::Core.MethodInsta else m = mi.def::Method mnargs = m.nargs - n_sig_params = length(Core.Compiler.unwrap_unionall(m.sig).parameters) + n_sig_params = length((unwrap_unionall(m.sig)::DataType).parameters) if (m.isva ? (n_sig_params < (mnargs - 1)) : (n_sig_params != mnargs)) push!(errors, InvalidCodeError(SIGNATURE_NARGS_MISMATCH, (m.isva, n_sig_params, mnargs))) end @@ -235,7 +238,7 @@ end function is_valid_rvalue(@nospecialize(x)) is_valid_argument(x) && return true - if isa(x, Expr) && x.head in (:new, :splatnew, :the_exception, :isdefined, :call, :invoke, :foreigncall, :cfunction, :gc_preserve_begin, :copyast) + if isa(x, Expr) && x.head in (:new, :splatnew, :the_exception, :isdefined, :call, :invoke, :invoke_modify, :foreigncall, :cfunction, :gc_preserve_begin, :copyast) return true end return false diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 35cf88e269c51..6cb4d69b0b746 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1980,24 +1980,23 @@ setfield! These atomically perform the operations to simultaneously get and set a field: - y = getfield!(value, name) + y = getfield(value, name) setfield!(value, name, x) return y -``` """ swapfield! """ - modifyfield!(value, name::Symbol, op, x, [order::Symbol]) - modifyfield!(value, i::Int, op, x, [order::Symbol]) + modifyfield!(value, name::Symbol, op, x, [order::Symbol]) -> Pair + modifyfield!(value, i::Int, op, x, [order::Symbol]) -> Pair These atomically perform the operations to get and set a field after applying the function `op`. - y = getfield!(value, name) + y = getfield(value, name) z = op(y, x) setfield!(value, name, z) - return y, z + return y => z If supported by the hardware (for example, atomic increment), this may be optimized to the appropriate hardware instruction, otherwise it'll use a loop. @@ -2006,18 +2005,19 @@ modifyfield! """ replacefield!(value, name::Symbol, expected, desired, - [success_order::Symbol, [fail_order::Symbol=success_order]) => - (old, Bool) + [success_order::Symbol, [fail_order::Symbol=success_order]) -> (; old, success::Bool) + replacefield!(value, i::Int, expected, desired, + [success_order::Symbol, [fail_order::Symbol=success_order]) -> (; old, success::Bool) These atomically perform the operations to get and conditionally set a field to a given value. - y = getfield!(value, name, fail_order) + y = getfield(value, name, fail_order) ok = y === expected if ok setfield!(value, name, desired, success_order) end - return y, ok + return (; old = y, success = ok) If supported by the hardware, this may be optimized to the appropriate hardware instruction, otherwise it'll use a loop. diff --git a/base/expr.jl b/base/expr.jl index 84b521543111b..2bc59717fea47 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -478,7 +478,7 @@ result into the field in the first argument and return the values `(old, new)`. This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call. -See [atomics](#man-atomics) in the manual for more details. +See [Per-field atomics](@ref man-atomics) section in the manual for more details. ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -496,17 +496,20 @@ julia> @atomic a.x += 1 # increment field x of a, with sequential consistency 3 julia> @atomic a.x + 1 # increment field x of a, with sequential consistency -(3, 4) +3 => 4 julia> @atomic a.x # fetch field x of a, with sequential consistency 4 julia> @atomic max(a.x, 10) # change field x of a to the max value, with sequential consistency -(4, 10) +4 => 10 julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency -(10, 10) +10 => 10 ``` + +!!! compat "Julia 1.7" + This functionality requires at least Julia 1.7. """ macro atomic(ex) if !isa(ex, Symbol) && !is_expr(ex, :(::)) @@ -574,7 +577,7 @@ Stores `new` into `a.b.x` and returns the old value of `a.b.x`. This operation translates to a `swapproperty!(a.b, :x, new)` call. -See [atomics](#man-atomics) in the manual for more details. +See [Per-field atomics](@ref man-atomics) section in the manual for more details. ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -588,6 +591,9 @@ julia> @atomicswap a.x = 2+2 # replace field x of a with 4, with sequential cons julia> @atomic a.x # fetch field x of a, with sequential consistency 4 ``` + +!!! compat "Julia 1.7" + This functionality requires at least Julia 1.7. """ macro atomicswap(order, ex) order isa QuoteNode || (order = esc(order)) @@ -617,7 +623,7 @@ replacement was completed. This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call. -See [atomics](#man-atomics) in the manual for more details. +See [Per-field atomics](@ref man-atomics) section in the manual for more details. ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -626,22 +632,25 @@ julia> a = Atomic(1) Atomic{Int64}(1) julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency -(1, true) +(old = 1, success = true) julia> @atomic a.x # fetch field x of a, with sequential consistency 2 julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency -(2, false) +(old = 2, success = false) julia> xchg = 2 => 0; # replace field x of a with 0 if it was 1, with sequential consistency julia> @atomicreplace a.x xchg -(2, true) +(old = 2, success = true) julia> @atomic a.x # fetch field x of a, with sequential consistency 0 ``` + +!!! compat "Julia 1.7" + This functionality requires at least Julia 1.7. """ macro atomicreplace(success_order, fail_order, ex, old_new) fail_order isa QuoteNode || (fail_order = esc(fail_order)) diff --git a/base/gmp.jl b/base/gmp.jl index 3e80c2bca8bfa..06409dd0cfab1 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -94,10 +94,10 @@ const ALLOC_OVERFLOW_FUNCTION = Ref(false) function __init__() try if version().major != VERSION.major || bits_per_limb() != BITS_PER_LIMB - msg = bits_per_limb() != BITS_PER_LIMB ? error : warn - msg("The dynamically loaded GMP library (v\"$(version())\" with __gmp_bits_per_limb == $(bits_per_limb()))\n", - "does not correspond to the compile time version (v\"$VERSION\" with __gmp_bits_per_limb == $BITS_PER_LIMB).\n", - "Please rebuild Julia.") + msg = """The dynamically loaded GMP library (v\"$(version())\" with __gmp_bits_per_limb == $(bits_per_limb())) + does not correspond to the compile time version (v\"$VERSION\" with __gmp_bits_per_limb == $BITS_PER_LIMB). + Please rebuild Julia.""" + bits_per_limb() != BITS_PER_LIMB ? @error(msg) : @warn(msg) end ccall((:__gmp_set_memory_functions, :libgmp), Cvoid, diff --git a/base/loading.jl b/base/loading.jl index 84ad47517cc31..bf444831363e2 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1926,11 +1926,13 @@ function precompile(@nospecialize(f), args::Tuple) precompile(Tuple{Core.Typeof(f), args...}) end +const ENABLE_PRECOMPILE_WARNINGS = Ref(false) function precompile(argt::Type) - if ccall(:jl_compile_hint, Int32, (Any,), argt) == 0 + ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0 + if !ret && ENABLE_PRECOMPILE_WARNINGS[] @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0 end - true + return ret end precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) diff --git a/base/pair.jl b/base/pair.jl index 7481d50b7458b..b5dffbb4e7e86 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -1,18 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -struct Pair{A, B} - first::A - second::B - function Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} - @_inline_meta - # if we didn't inline this, it's probably because the callsite was actually dynamic - # to avoid potentially compiling many copies of this, we mark the arguments with `@nospecialize` - # but also mark the whole function with `@inline` to ensure we will inline it whenever possible - # (even if `convert(::Type{A}, a::A)` for some reason was expensive) - return new(a, b) - end -end -Pair(a, b) = Pair{typeof(a), typeof(b)}(a, b) const => = Pair """ diff --git a/base/show.jl b/base/show.jl index f3465a25d4abb..1f75c5481a86a 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1832,7 +1832,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In elseif head === :hcat || head === :row sep = " " elseif head === :ncat || head === :nrow - sep = ";"^args[1] * " " + sep = ";"^args[1]::Int * " " args = args[2:end] nargs = nargs - 1 else @@ -2378,7 +2378,9 @@ end function print_type_stacktrace(io, type; color=:normal) str = sprint(show, type, context=io) i = findfirst('{', str) - if i === nothing || !get(io, :backtrace, false)::Bool + if !get(io, :backtrace, false)::Bool + print(io, str) + elseif i === nothing printstyled(io, str; color=color) else printstyled(io, str[1:prevind(str,i)]; color=color) diff --git a/base/timing.jl b/base/timing.jl index ab7af23048305..45a27e3378977 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -55,7 +55,7 @@ function gc_alloc_count(diff::GC_Diff) diff.malloc + diff.realloc + diff.poolalloc + diff.bigalloc end -# cumulative total time spent on compilation +# cumulative total time spent on compilation, in nanoseconds cumulative_compile_time_ns_before() = ccall(:jl_cumulative_compile_time_ns_before, UInt64, ()) cumulative_compile_time_ns_after() = ccall(:jl_cumulative_compile_time_ns_after, UInt64, ()) diff --git a/base/views.jl b/base/views.jl index f60dc04094a43..e26359a5c9fd7 100644 --- a/base/views.jl +++ b/base/views.jl @@ -42,7 +42,7 @@ function replace_ref_begin_end_!(ex, withex) n = 1 J = lastindex(ex.args) for j = 2:J - exj, used = replace_ref_begin_end_!(ex.args[j], (:($firstindex($S)),:($lastindex($S,$n)))) + exj, used = replace_ref_begin_end_!(ex.args[j], (:($firstindex($S,$n)),:($lastindex($S,$n)))) used_S |= used ex.args[j] = exj if isa(exj,Expr) && exj.head === :... diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 94cec50ae7e6a..d921055f08221 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -31,12 +31,27 @@ void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char * /* Wrapper around dlopen(), with extra relative pathing thrown in*/ static void * load_library(const char * rel_path, const char * src_dir) { + void * handle = NULL; + + // See if a handle is already open to the basename + const char *basename = rel_path + strlen(rel_path); + while (basename-- > rel_path) + if (*basename == PATHSEPSTRING[0] || *basename == '/') + break; + basename++; +#if defined(_OS_WINDOWS_) + if ((handle = GetModuleHandleW(basename))) + return handle; +#else + if ((handle = dlopen(basename, RTLD_NOLOAD | RTLD_NOW | RTLD_GLOBAL))) + return handle; +#endif + char path[2*PATH_MAX + 1] = {0}; strncat(path, src_dir, sizeof(path) - 1); strncat(path, PATHSEPSTRING, sizeof(path) - 1); strncat(path, rel_path, sizeof(path) - 1); - void * handle = NULL; #if defined(_OS_WINDOWS_) wchar_t wpath[2*PATH_MAX + 1] = {0}; if (!utf8_to_wchar(path, wpath, 2*PATH_MAX)) { diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 278dda2812559..bb760f70ab867 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -250,16 +250,20 @@ function generate_precompile_statements() module $pkgname end """) - tmp = tempname() + tmp_prec = tempname() + tmp_proc = tempname() s = """ pushfirst!(DEPOT_PATH, $(repr(prec_path))); - Base.PRECOMPILE_TRACE_COMPILE[] = $(repr(tmp)); + Base.PRECOMPILE_TRACE_COMPILE[] = $(repr(tmp_prec)); Base.compilecache(Base.PkgId($(repr(pkgname))), $(repr(path))) $precompile_script """ - run(`$(julia_exepath()) -O0 --sysimage $sysimg --startup-file=no -Cnative -e $s`) - for statement in split(read(tmp, String), '\n') - push!(statements, statement) + run(`$(julia_exepath()) -O0 --sysimage $sysimg --trace-compile=$tmp_proc --startup-file=no -Cnative -e $s`) + for f in (tmp_prec, tmp_proc) + for statement in split(read(f, String), '\n') + occursin("Main.", statement) && continue + push!(statements, statement) + end end end diff --git a/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/md5 b/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/md5 new file mode 100644 index 0000000000000..eee45b02bc589 --- /dev/null +++ b/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/md5 @@ -0,0 +1 @@ +4f5b2832ade28f50ecb0a97bbe313749 diff --git a/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/sha512 b/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/sha512 new file mode 100644 index 0000000000000..e832a841c1f77 --- /dev/null +++ b/deps/checksums/Pkg-9f30b81e367d7fed6ae13577cd8a434fc30e625e.tar.gz/sha512 @@ -0,0 +1 @@ +df42d57a4f438e193283e35ff307725ce548bb5742613135d63658dc070ce41d43c1d71545f64e8f0c0feaffa6b7cf9e12d5ff188c6bad83bb2ad1fb4118e177 diff --git a/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/md5 b/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/md5 deleted file mode 100644 index c44d4e836688b..0000000000000 --- a/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -c848e464b9643d5396cd51ffe492f0b3 diff --git a/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/sha512 b/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/sha512 deleted file mode 100644 index e5c06cafc4e8f..0000000000000 --- a/deps/checksums/Pkg-df2476a01c01cdadd553dbf010b21fabe7e211d3.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -4ceb13b64bf66ae50fb0ee87e3dc3e57a52dbdf68592226d36c8d37aaa6179d205de269073fc08be137430deb6c06c6cda65f3e0215f9a32854f1ea07be7ca26 diff --git a/deps/checksums/openblas b/deps/checksums/openblas index 4b4d477ddc3d2..5a1668aa45254 100644 --- a/deps/checksums/openblas +++ b/deps/checksums/openblas @@ -1,92 +1,92 @@ -OpenBLAS.v0.3.13+6.aarch64-apple-darwin-libgfortran5.tar.gz/md5/db35f9fcf744a3d86c0a20b8ab39c7c1 -OpenBLAS.v0.3.13+6.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/45ffdac8aa4150e96981cdc3d32242ebf7b7987fed5408d6cd6a9a6518cf7b7f204ca2a4226a0837cae76c2d4d4822d4ae93d2de1164428830c04e2147976341 -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran3.tar.gz/md5/aecd6bc6356314108c4ef2bfd768d006 -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/1f5e861d3e14d64d0574a4dde4909f21cc0c22d1bfe052aae93a7ba3b3a9e564e01a7e1c921cb6175ba251a8aadfb0c4fce345803966dd7452db3d289bf144d3 -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran4.tar.gz/md5/7d20f2dd20459cee45adb024adb43efd -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/b06dcbf820f74e3e3cda4cfdf76113e7f518639642f141dac13ca19b05fec09160c3728fb4f7b001a9aa63300f6e289cd2126da1179ef4efce18e814d8c32bbf -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran5.tar.gz/md5/5f0a683b55fc2f5a4dcd134a0d03c175 -OpenBLAS.v0.3.13+6.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/89c25976dd89e2e1c856790aaa4d0951f912fd7ded92223952316e40b08e4b9d9218b25a35cf9ab19021b356ccbb72c8bab2237bc62b8dac37317abe31edff6d -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran3.tar.gz/md5/5a7815b5981d30b89cb48a3e3bbf8f4d -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran3.tar.gz/sha512/358b7d25a069d50434b6621d1831903b88f6e120f10b5978235cc82f795da4d31ca4e6d02eb5eb1fd5587085828e95835e2ad84b2042865c552c5493cc272227 -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran4.tar.gz/md5/02062032841900e941cfc66a0ef94dae -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran4.tar.gz/sha512/86f3072c3b8e36f3b33d90da755bf9d2a95ba0317852eaf1c74deb8a0f62a2b5c19a3b1d551c054536277da50865ef341c5e05fbab195edc4cd1fb160b4203b8 -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran5.tar.gz/md5/ec50a9a3e5078726d6e3dd011b7a4713 -OpenBLAS.v0.3.13+6.aarch64-linux-musl-libgfortran5.tar.gz/sha512/548d4b893648de6c1a3d6b24f4531c4b190afc338887d1b8eb9040a9aae72cf846127e9c545841568a2f358f090451e13cf12a191456d4c27431661ca41f6e10 -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/15cb058e906a1f042d51e8dcc44dac90 -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/d74ba1d83199259b07787424832318e31013384d4f7217f6d7adb47dcbfe0836147997c044c8ca7a27b5a5eea435948a42d7f81a38014b7f7b3f4fb049e3578b -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/14b991b59eb27538331fae0544130d8a -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/d2c194692265325f9e5b6c09c23d7dcb45f1f2ce88edf2fbe6f9b021bfedf6b0c7c4b94a7ff5aa7095b7a131870759cd81ec80369e315660f5cbb0ac1c133e76 -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/161a6506630eb035bc6afae69aea91dd -OpenBLAS.v0.3.13+6.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/f64ce6bbaac4e16d5956b6298204628648b35e76f14a528aa8df815b0021053e4e1963438edc7e5f66fd82ea1e1d7bc38b14c52ad0ea7b90eeb1ee59d0927fd8 -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/b4d102165aff04f4a3ff583c754ec90c -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/f488da922deaac3fa42f5637003c9dbfd943aa267104e6fce46b77fd9f10dfc580191bd5aa4c97bf5b41ad6a92fd669daca8b11479a3a7e28f41047826f0e6bd -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/9411f83736cbcef0b840914ace71d869 -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/3b0c9077255fa639d8798193fb1c5fd8ad7824f58889d0c99b388b3ddc7622122387acc49fc29f7c5b5a62ff7dd2335a47b6e60c14d613ba37e11b79faddf7d2 -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/1222f66dbd5eb8dc910efe04d37fb763 -OpenBLAS.v0.3.13+6.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/a747df8a04d50ef4a4b90bb66e682cd7414b6d2f0cd9577e25b18c80d36b599e9506e8fcf24729a8bc0f5ef464c57d86a87e1e74140597466dbd862eeb9a0b18 -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/35fd828c77d3e1817bebef49aa045f02 -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/fd4ce90ea21f64abde4497d3d6518c341383eae4c8f5052951b5c1469c87f1464cc1c57f7047bd4881b55d70d6453ef558e6d6e1986fe463a98a0567bbb876a5 -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/deaa63f74369dbf358946c6796e8bd6b -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/7b16a7f5b5710de0b38122af6ed9e4a6b3ede4cd9c18c79314fbde366ca92c2dae17d1ab9e43213b5a6f80470455afbb06d54ff326e0404d60f5454164f2c62a -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/450506080f49538628dc2407461b894d -OpenBLAS.v0.3.13+6.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/95dc7f14c1b1f450de59a3f95673dc510bcd0e38b6d82a8657d4dbdd97158d2095002a61ecb4a4c514e530c0a9879afd232f24a71561e8516683c564406a0a55 -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/cadda67c770ea3835170c63cf5c1a93f -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/ccd326df1d3ce8e138fc22db37880a0f15b3b5740b75f4d6e54c6496735dea48d1011c31d0fbf6fcaf7f4ccc565cb2aa59bac473b9b12251da1adaa992998373 -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/e89c9935ed19d9b6bedd1b70cbe1ea27 -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/d537e954d424240315280fe632bfa83088825dd770042a750448e1553b2887a8c3d4edf193c89d2bccb7b0c3eae560937156eb989373accca1dbecee47e32cc4 -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/7072bd88910ce5402e18527f178dcd56 -OpenBLAS.v0.3.13+6.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/0e4e038f317faa7a14cc29267202ad781a2551ef444b27f841ad2a39f5fb5032d20d50749d1b5a925e6552247aca40d84a1464c268022d8b9560c6e6fcf9a9bd -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran3.tar.gz/md5/261636c2b2b3a734e0d054b67fc0e617 -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran3.tar.gz/sha512/0777c0cccb6f688024756e12f8a09ca107cf6f2408d04fb1efeae67299eb8de834de158b9ada232e3e50d4bb0481810181c54f6b63238ba8d4f1a779bf30ceab -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran4.tar.gz/md5/af9998d911a0919bbc611279f6957d8f -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran4.tar.gz/sha512/639d0d837dd62f4eff32071e2ef5d95d3d1a80995dc9da0a97e0a2f8bedf4637e3082acec309744d0d36dca8e82b3f7bf792ffb9ba47c18d8b9a44aa0f368adf -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran5.tar.gz/md5/7bee1a7c3470c32c10e3776289ce730f -OpenBLAS.v0.3.13+6.i686-linux-gnu-libgfortran5.tar.gz/sha512/ff76d5fc5ff2432dfcd9a36cfb95943fecab3e75153c12260b729a89c6bc2269f7f0ad256f6334d58445de27d32f6073e830cee4a59e9196a0b7395c3a3b7ab0 -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran3.tar.gz/md5/362e299c65ed4011563caf8555f55738 -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran3.tar.gz/sha512/45eeae6bc817e8d78c0daa69ca2add3c32d714766e1e1341d14c445a1beb5a5a7ae93e88649c9a62f07c5463b6ee300b60acc06d9d29974cc6725d08d9df66d9 -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran4.tar.gz/md5/791075ccd19280d58209f48b487ec42b -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran4.tar.gz/sha512/44b9bf0b5d31048fe05f78a71fe9ddee799bd70f7586061fdd9a1390a894701eb96678ad9c332a21f2c2b079896924bee14d64ea89f6314babae1faac289d6eb -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran5.tar.gz/md5/712e9c7ef4640dbc150371ef3a10e249 -OpenBLAS.v0.3.13+6.i686-linux-musl-libgfortran5.tar.gz/sha512/3407fab09ae6e2b12c2b586915557d121bfa345a4bf66597bec2d5850ce33ad70dddb45ad08a975097e2a428e65abffdbd9747f1b46fa944bc52218798fd2e34 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran3.tar.gz/md5/93d7254e1e03f4ef1acb6b4e8d63c813 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran3.tar.gz/sha512/198d4d0455f981345f20ff4a196cca056fbd7c5fd4d6a2b11e0ec6ba695c362d309947b9fcc13a6c51a44cc3ea73e559c0246a98b26fd6baa6cf07a055f5c972 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran4.tar.gz/md5/728d9f80b9e6b5ecce0ffab86b7e1c52 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran4.tar.gz/sha512/1b8fc2e3e14fb172ec7d99d5beef54bcabdc807318f1b0415f1bdf7bb97a1e49c20168a9bfc0e89f4f9367dfbd1011e3cffe74b515da53fce00f06896387ca72 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran5.tar.gz/md5/b989478ab0496a27daf87f8ebb396316 -OpenBLAS.v0.3.13+6.i686-w64-mingw32-libgfortran5.tar.gz/sha512/c56ae711ecc9c6fe9e65e7610011f7189ecda4c0e94cfdd6bb150a32eac6f3d2343c671005f4008873e2f026fa312ce0257716a47fb4e91f82a6d29013dfc303 -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/194ec8e4078fc6624acfefb29a9a1177 -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/ecdd5b17232ae08e76f6822ec52cc96e4b5cde0748baf799799aa7946966b61f83c5b1d8a70e4f14b4e074e13e0cc72f2261f2a304ab8d8be15e68a004210be1 -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/f08aad57a0d92ba7811b40deb7c40e5a -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/f5759dfce2854f929a73e11253edd37e100b9437829eca893f97a2c08a7ebc7af4815f588466cc8230985932f47b150e671d3a822e8463c1461bc3ce698f222d -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/bf291c76d9c9642e6964141eb541e4e0 -OpenBLAS.v0.3.13+6.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/0792f5d3c4c7f1ff5f43bcf6aafc8547c742e969ef4fc056f098649f7d99470538827349e5f39f0ce81ac15ec992f11d93a78f1ea9673a67ec076787b6d7b9c5 -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran3.tar.gz/md5/9a1979528b2b54df3012e2182b834bbd -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/1752e0ee45107eec916a42370e19b6091b41423eb0f9443f23f78c3e8dd8db5fa0b8b72f5edf2d26e759e0f44056034dde1bce38b9c12f58d6c931ec873bd67c -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran4.tar.gz/md5/1b30b010ee8ecf949d83d98be7cd59a0 -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/bab954ecbc2e9ece41807409bfef66063dc98cc7fbdbb0bbce24a331d5b121b0c63432a13cea935c5c27090f790e9fba599e1c129e0005656952805260732da6 -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran5.tar.gz/md5/da031443b1bd5ed8abb8e956a05c616c -OpenBLAS.v0.3.13+6.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/0009d10265ff16603c8552663b3c71ab619905b18fe87119a3203fe24d531148b8b18f727260fc125362c58a6226d1dca98a6517e9b7a93418a2cdbb2c66806e -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran3.tar.gz/md5/133b638a2efa22381cd70abe871e6ebe -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/98067cbaf1f5cf4a6ba01cf09ec9de044c04007f3a1953e51a75439cfb7215caa5b1a7f1b848b216926231a9511c45e78ba78abd39da06c6fbec4ce9542890f2 -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran4.tar.gz/md5/3590e16f503a615a8c8886af39d3fd14 -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/b7f3fd487e44a4f6cbbf035bc9fb433aa761f05bc1cf0c5351e6f9a9e5b80450ffbd11f86f904477c89aadbe24e22780ce108e228585e701d92141a735b454fd -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran5.tar.gz/md5/05472a418ff1d7f6bedb58894d6f5356 -OpenBLAS.v0.3.13+6.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/fc8a84b28db834b93a0c9a9c96ba22dfc6018cba90c0d43f4e1db7fcbda73c0aec04d7347db02b94df5375e785d447b3aeb993bf0ded69e5d43c2486c13b2aa5 -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran3.tar.gz/md5/22200029744717079b3b8663d683273a -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran3.tar.gz/sha512/664bc2e95f10ac5668d51a2ffae488ad002f00995e5e7b620dd894e816bcaeeb7ccffb45f448365484f97f7aa5ac7b237ca1767e2a9421fd5c5fa39098c9fcb4 -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran4.tar.gz/md5/b9fb6101fa172dd0f1a00c07673b308e -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran4.tar.gz/sha512/cf49792da8bc3e3a971b0361f2bdd835db46764c308d4ad0e20215c8bba5d6bd9b96e9e8fe2cdfb835bba4f21e62287f7b67245ff1d00a9ef3f9e44201b53412 -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran5.tar.gz/md5/17c0ab204c65b252988bf873226f003d -OpenBLAS.v0.3.13+6.x86_64-linux-musl-libgfortran5.tar.gz/sha512/02f493a6cb20c51c38203928a5a9e4890fc9285ce1907a552b61bd96bc64bc50a1932236d7617e83edc5ae1c40da84cc1d8db80c190605676869a8d1a57c4d7e -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/24e787f88452b2f304c269061ad07b0a -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/d45272120a6e15431b9a08afe5648afa903b588e2d65541f80ce123117dfc0e6d3b620ce4063211a420f1cfd398e969be69eb6a6302211fc368c4af3c9d6d3ef -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/9aa8cd86c2de41ed2ed47bccc315f19f -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/1c42e55fef774a34d3b0e0b0f899418a501cc9d56c4d38cfa0b4823a7622c7eb594f4ab222bd6994ba1c1eb7b69a37b10ec78b206a24d54276b03f69133b7b40 -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/e09d926e41b3a52188cac7efe9d9aeed -OpenBLAS.v0.3.13+6.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/eddc11f4b5535e629af6fe2705f24b142e457fd7721d6f9892e1c951d2722e996f32a59d05df803bc7a77c15ae011cc5f36a88709a7ebc9e6be00cd52789083b -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/5f09322a961185e965f8914b87fb769c -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/531860456a4604d7743b52632ca1562448e3b34015e0a7082935a12fe7537c3824fd6eca29813b8b28043c85db4c748ca2e42dfb443149e225b2ae1ebf641ece -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/68bf07ec07fab8eb000742f5b34a297a -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/5cf754e09737a9ccf67998a0dd64a6eb836784489b337bd9cd3379773ccc0d8261f6eb91ae6811dc45f3dd13480c6e0abc603f13add94bc5505ed4aa41e82951 -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/d30d1b10c1a98ecbed686a1d133f4abc -OpenBLAS.v0.3.13+6.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/6a61cd1eb2b20f33bb6370d760cf98c8c3af2f323b3c83c866ab8d2e3010771da7345fccbbb94880ca0c0956b711d3127566f040bbb79166e281b9ea6d14b2c7 +OpenBLAS.v0.3.13+7.aarch64-apple-darwin-libgfortran5.tar.gz/md5/c9800f7e24105c92a5205c77ddbc4227 +OpenBLAS.v0.3.13+7.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/43703203a6d7c7aff8c8a257a2f5393d930e7e12064ff9a7890114dc250c28f0c73d735c4bbe09a811610cec6f4fe5079a806fb5898025c92067239dc70d6f62 +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran3.tar.gz/md5/ffb49e047b54a68b9de82ac3ae64a94d +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/745f30477562ec0bcf754a36e0c0082a60afea518dbe9fc46f95ee7547c33eece8717f6c9ee54bf1420a221ab7fb779dd4ccc98ca74164315975b7ac8e8b6979 +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran4.tar.gz/md5/ccdd01f36e19f8269750c93901c54bf7 +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/ef619a6b5d140253a5f310f975d1aca378a8419f75b90713baf73aa1f3be9cdc43ae96a8fc40bc7d3502947bce87de4fd5d869f16220bde1ced4e6730437ea5e +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran5.tar.gz/md5/cbe589a28fa89287502eb591d26c0a1d +OpenBLAS.v0.3.13+7.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/7d894dda5e3eb124e17dcaa6ca7b02e29177f2ac678e18b6e57f6ce34c654c0dafb81fe470e93a42fa4b63f904b2cee00139cc149c7868d3f70b69313242187b +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran3.tar.gz/md5/ebf835b3264f4b63cedac8c10f65316c +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran3.tar.gz/sha512/2de3869ae6505789ff2ebbc83672ddb4f7a3f1610a88040ac9407dbafd23b7a0bdf19328964f4d901e7389636564fefb82442a1bb89a0d18e4a6441267d886b3 +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran4.tar.gz/md5/3d20fe5e4c37e40eafd2a07ac5b11cf8 +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran4.tar.gz/sha512/9dadabace2cec1260b2a02b42102453fa4c7e832c89d5c4f06b8a58ead30c2d926e6f615da03016aec596324619d34b7e25dec88e1dfa9725e420fbf6397c0ca +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran5.tar.gz/md5/53c4a96fb47aed019efc780d0e9e4929 +OpenBLAS.v0.3.13+7.aarch64-linux-musl-libgfortran5.tar.gz/sha512/2e3ebec1e64f29308d7e61e80ff95dd7ff45a7bf7696639f6b786990d4e183c5cb8357e65f16b9c98058ff8d197a3e1e7c355a304e56c644fbe8b321d8941988 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/59142e0c7a6e50c27e084f10982402c2 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4e45b9a7087840edaf515af3a9ad15f6d5c2cbdb8be244b9c65542f6f6526e5f82fcd0939070b35a19a8ec1ea7c8886c82070359d74924b90f210ab3026b1d24 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/2c00dddb46cd74324c023d9cea5d12e0 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/e4b6b07016c59727be3cc01fbfe1ceb2fc19bbddf96bffbd8a0b8bbf10e30768a7d8ed04052cbc53093d8f6d6f8d57c0d05d6692ba456bb8aa31a0f736617d33 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/d98b02bba19262ebb4a6eae0971e19a8 +OpenBLAS.v0.3.13+7.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/f73413a520bf204d9c369dbc910e0df4b72e583ab31a66e5e64ae31e93464d6f8cc114be7c9f15738c900fa8762905f2c9ce198c45426eab22b119ac8fc489d3 +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/5e34312709f68e06783e890d94447646 +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/f127d0df4a0b4d91c810990108f0f3daf89373f29002249edd6225330bdc804a0a2282e05ab128b15ec58c75098213481e26bb0e9a5a2b642da9c43457b9f9f9 +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/02d6189238fe4ef41e791f558324e32c +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/fd5d4c2f43c4e98f0c28afdc43f2dfc97123fbe723c577688acaab959e96980c14b0ae76d150807da1814f9ea55dbc21a9cce9454d52804ba815cf8026d17117 +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/14d77e7332ed0949342a4b659ce98088 +OpenBLAS.v0.3.13+7.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/354113f9877ecb04dbdbc105991ee20c58d9bd49d499ee476af87135fbd277c7729e909e528843b5fd4ae2f076996ea74284ad384fc06584882ea5fdfd7d4206 +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/b32a6caa13c26d964a456f440a2df0ee +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4b19666ca3a32c1a145f89eed063cafea2eb468cafc3b47168977749a8e484e124b5008c58d00d40e4c7ba1b09669d7499fe259bd43c88ed0d064c326c85dea9 +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/4b15013643544594e7a8a4948b9a4db3 +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/2fd0e4d5ec22e3ef831a60909cbf803ba88e16b7ba12269cf22bd14808fb25dccad597e0690530b704dc971c681f1c075f3663f4d7cb05998835e584cb9779b8 +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/7217154630d07a83fa84cbaf298d84e2 +OpenBLAS.v0.3.13+7.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/a9cded7959ef98027e1da87127464ac930dec3ba9c198296bf8c64918d31a8fcdd92d4d74e3aad2f3aff02553959e141986870873e01a5302c67e7efacc33616 +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/65f601ff699b51719aacc4a495be5b82 +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/c96e4fadf74eea0c74ccc7d57670222d6bc097ceecc38211a742bdaf8c48491266302a96a33317ab4e8e19669b41f3051c5ca409c076ae1993f5218fa30f2cd6 +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/7d0f0e432a372ce94cd127153b8976ee +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/b017bb4ffe9caec8410c72a80c5307e81cbfe84e4c148f3f99ca774efd17b47378b24ce95cfe6df45e8ee890ff0c326d480fabfbac0e494e25f620e82fdbbb8e +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/398c894c743c181b4edcffebb5d91340 +OpenBLAS.v0.3.13+7.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/c4c7acae9dfb49252dab6a7d0217e8caa8df707da72fcab3dcb5ea6449e42effd1a9df18531e8077d91ad8fdb323482a7fa405eda1a4660f48e59927ef33f525 +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran3.tar.gz/md5/ac7f07c75d419712e4ddad380fda0177 +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran3.tar.gz/sha512/4522c5b5924ff976e58ee30bbd0bf933baa2f705e3d68a83aaeeabaa8cd4cacf41edf05612f4616052d78ce04e70d62a411f8e0cc7ab9ab3fbc56dbae2615b67 +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran4.tar.gz/md5/08254b64198705d5062db0526d6d8cde +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran4.tar.gz/sha512/5d8c1062f15b11066d7400811492b8e2e1c33150bda4b74d7f9d7cd64529f0c0b89d7a1a2e644dc321be18fd3e3ba7dff92fe597f3d42aad5d903c8b26fa0e87 +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran5.tar.gz/md5/5f610eff8a8e7b24868a2b915419d4a3 +OpenBLAS.v0.3.13+7.i686-linux-gnu-libgfortran5.tar.gz/sha512/8967a180b57ada258aea76a39b8c71b718314460eeef2bad1e407be0c60a54f04a0b733944feb036c5fd2d67a8f3761babef10530dd871006f7d7ba366b4c95b +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran3.tar.gz/md5/1867e826c4cdc6b6964bf2e7be43671e +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran3.tar.gz/sha512/c7e7206361a4ee379c40a483aae1dc5357b8ba77e561fbf961cd7269674705c83ba2b5b09891b4567a330f9621d5a7bc0c323c8ec9d2385222b4afb57e010f8e +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran4.tar.gz/md5/5a4511b55512387315e42555a1f35222 +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran4.tar.gz/sha512/3381a32dd47d7106a131f8e6be89675c29b8ff496336e8f4dbc5bac13df3b1de9bd03a2c027b94ee43d66f4cb8fcab1545327552ac45280252046072dde3ebd7 +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran5.tar.gz/md5/9766d92bb2a0bc7376d42537c0bff642 +OpenBLAS.v0.3.13+7.i686-linux-musl-libgfortran5.tar.gz/sha512/34149b0ae5ab26d4b5ec9f5a8a981eae713e355248c56af0d71eeb2720cd4e453d96fb9e885a48c94389759f5e015536b7a21d9720c6b46b167e6bbbccc717aa +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran3.tar.gz/md5/3e13fa70512da79cb19d15899e967e8b +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran3.tar.gz/sha512/c4e99d0bcfd9de4ddcdbf819872257a91e45cd778c7bf927b6a3c69e24d555d6a757236b08113160790a4a8fe0a3b258c17c8d43386a0d9ecf926e4e542b9a5b +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran4.tar.gz/md5/724db97fb05d2418325b84eff736b0d6 +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran4.tar.gz/sha512/4869bc155e2c5bbca2ea6a0107a2b12e7d7ec226dfa5ab81c8f46751f5e2a9342127c0c8ed24731cbfa05904db319ffdd907b248b725043d8bd089e911cf6808 +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran5.tar.gz/md5/4fd5c17e2432dfbf2fbf6930c9b08617 +OpenBLAS.v0.3.13+7.i686-w64-mingw32-libgfortran5.tar.gz/sha512/92c4b8b61d9d4dfc8cc60c05cc1a8663e2cc33f3e0683d562f723939697237cfaf3925eb903ec2b443e1a943260c91e04029e582f9f07f2b3655ee5ed9812dad +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/6b509bdecd697472c5c96b947a3d016f +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/494b8fa97c383554edd62d04b985f2fe25f1365d149af041d1d2d9c617df0fe12fff271e21a3811205e05412229e2f55043876ba6b553f22a7616653928b1908 +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/5be5d102fd4c0537403a0fa8266a95d2 +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/2fb952a7da419d64f41b5fdff7c49e8e750f8724348180b52e61a50b4b55e5a3c3d072455d3ce870fb240b9c50c6f4572f6211813edb965ca41fa27efc85de6a +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/9bb7b2ac52789cd7eba17e4b564d95d8 +OpenBLAS.v0.3.13+7.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/a50700ab72ab169c91bd10eccb4a03231d0e223bcf3e158331af4a6805e2f17ab042eb6c9db335a800ab5b69554c0b9a5aa78c1f112c8b579f5148013afa15c2 +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran3.tar.gz/md5/2bfd33949884b29e2621bd4db9ea790b +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/87e32e09c5fb6da7396a13646a19ddc9f0aa6070060fba1e4f4490fc447c041a0b6ae68cd338e1f1b2d610bf61e28274823c81ae229768d6825104337d82e3d5 +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran4.tar.gz/md5/e0a3f443cd840d5a9cca3748c0fd247b +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/b4a55211a2a64fceb2eb89c1bbb06a5f658d4d81dcc6c67c1fc6a804ba16de8a05c341b81bae8b14788b85f772555785696a2b11b1cb6873ab9fbd174eebf1c1 +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran5.tar.gz/md5/3acd992bd55a527ff96cbc1fd208300a +OpenBLAS.v0.3.13+7.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/192300b6480efe523e3a196570743e3c091d9d29c6f16cb3f5f951b344e8ecc5a76f235c8fa2b2a9bd56c8dc394fca447c2bd1d4102ad7f9823ab71436154dbd +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran3.tar.gz/md5/35ede94f298e95ff772e3655692da81c +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/109fbc3ee23528640011df500f7843bec3f42d92b6be99e2101bfd0c6bb798d5c5a0c8a9d2567d11e50bdf54213347ea204c383660d2fd6445ae0735f210d211 +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran4.tar.gz/md5/5d97a16fb7ba45d34aee0d1b55f81008 +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/f957549494ec3c11fbb73cad4b43d8a39a7f0e509a3f797cd35898f40ed577aad27cc721a5720eb38e0ccd5064915a4ca93b67e517f7fa1cef86fbe3f88c2529 +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran5.tar.gz/md5/85903515954b13d71dfc1cfed5364000 +OpenBLAS.v0.3.13+7.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/ee3fdeede35b52987565e55c96a1a47964cf1da52b25753302c2ac4671b2921a70656a5f9e39bf1b7db0d6037c305f66b4aa22529713239ca30c322f5c3d8f97 +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran3.tar.gz/md5/b9a86f939ec7d76674d67653d3acdfee +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran3.tar.gz/sha512/8a9d10605d9fad833ece78bc0c4690e43050e5e276b0c76ff194b617134e05261db2b385fe2c4b0b2313a3be107731b4cc71dae79d6e403e64a6ca635a3d4b47 +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran4.tar.gz/md5/6a4a082c4d73cb6dad69460deb75afdd +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran4.tar.gz/sha512/8fa8c2f07ff044acc78caaf7e34f6e299567ac82436059c70d9a149ffd03d0d257a009592f845e22554a17dbd59fb13c23582a0e4131a186139c9be9c71b9f2d +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran5.tar.gz/md5/50148e4dc8881c9f1f4c2bc936c3f59e +OpenBLAS.v0.3.13+7.x86_64-linux-musl-libgfortran5.tar.gz/sha512/df0019201f71a12867deb872d3a3f7b3c07919bb779b3f3f5612d52f9c02bba1c19e230c81c8421e32b5cf1140dc388f1dfae3379c081cecb4a516f1d28a3788 +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/87b58444e006bb680e8139b2ee7bc998 +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/c32638c812a3cc4e3c3916b6a9bf572ac202b2101946a18a7af89de1cd3171a44f9d9308a6de822bd703acd96533c963ad8a8205a1200039540191cd03fe1f6b +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/f8a07c961fa2b6d268ff1b3f4e02b729 +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/b3faaa4155971047a8f58123da5dbf51cd6cac574a46e4a339b2be0570f1868fddbe9b46ce07f75fb561bb5da42bf19466035a05286832a85f1f39c58265bfb4 +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/67d69caa1877b74ca652f0e96718ddde +OpenBLAS.v0.3.13+7.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/bc72e2df6e0dec6962cd32b77f49ec93b05b34dbc95c0b0c31da6158d3a5f497f6321a895f3faa48c82ee956fbd8e82346bb8d8a20a9639e2e689f0b85ab5c2e +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/dc755ddb59511ef049daf85a38ef0804 +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/fa5a559165b97cc5ca24d5df02dfeb1f97b8619512d8f99e3c8c8ee800aec0d3ca56ac4170569dafee7cec738e9abc5017958c153bd44d19d60fbecac53a0ee2 +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/ff56c35358fb846827f8f869fdca4b21 +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/c8c0029c9b95f5a11dfc84bd02b944ed96de5c96835b2e6dc5e8c401fc37b667f8ea956f3715794b01be68ea86e0c74b4ebd22f728d1f9777516bfdca944c1b3 +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/e5bbc60da2dce3c4abe0583cff4b985e +OpenBLAS.v0.3.13+7.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/344dac288566886013b59c40877b879578b11f57b90433e65909b56b7e06416c0ceffedf08cc3d05e08514a098309ae492fd57c30dc97737cdede1aebed2ea2d diff --git a/deps/openblas.mk b/deps/openblas.mk index a1ce15100ac4c..ee6712d883de6 100644 --- a/deps/openblas.mk +++ b/deps/openblas.mk @@ -118,7 +118,12 @@ $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-Only-filter-out-mavx-on-Sandybridge.pat patch -p1 -f < $(SRCDIR)/patches/openblas-Only-filter-out-mavx-on-Sandybridge.patch echo 1 > $@ -$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-Only-filter-out-mavx-on-Sandybridge.patch-applied +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-armv8-volatile-detecion.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-Only-filter-out-mavx-on-Sandybridge.patch-applied + cd $(BUILDDIR)/$(OPENBLAS_SRC_DIR) && \ + patch -p1 -f < $(SRCDIR)/patches/openblas-armv8-volatile-detecion.patch + echo 1 > $@ + +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-armv8-volatile-detecion.patch-applied echo 1 > $@ $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-compiled: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured diff --git a/deps/patches/openblas-armv8-volatile-detecion.patch b/deps/patches/openblas-armv8-volatile-detecion.patch new file mode 100644 index 0000000000000..f1fb36b6a34ab --- /dev/null +++ b/deps/patches/openblas-armv8-volatile-detecion.patch @@ -0,0 +1,23 @@ +From 6fe0f1fab9d6a7f46d71d37ebb210fbf56924fbc Mon Sep 17 00:00:00 2001 +From: Martin Kroeker +Date: Mon, 11 Jan 2021 19:05:29 +0100 +Subject: [PATCH] Label get_cpu_ftr as volatile to keep gcc from rearranging + the code + +--- + driver/others/dynamic_arm64.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/driver/others/dynamic_arm64.c b/driver/others/dynamic_arm64.c +index 4f1b12f27a..37c0694b6f 100644 +--- a/driver/others/dynamic_arm64.c ++++ b/driver/others/dynamic_arm64.c +@@ -68,7 +68,7 @@ extern void openblas_warning(int verbose, const char * msg); + #endif + + #define get_cpu_ftr(id, var) ({ \ +- __asm__("mrs %0, "#id : "=r" (var)); \ ++ __asm__ __volatile__("mrs %0, "#id : "=r" (var)); \ + }) + + static char *corename[] = { diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index 658bec21bbfb9..246fab74f8fe7 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -249,6 +249,9 @@ orderings (:monotonic, :acquire, :release, :acquire\_release, or with an atomic ordering constraint, or will be done with monotonic (relaxed) ordering if unspecified. +!!! compat "Julia 1.7" + Per-field atomics requires at least Julia 1.7. + ## Side effects and mutable function arguments diff --git a/src/Makefile b/src/Makefile index 8da3e4e6687d5..f5cc50e6129e9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -259,10 +259,10 @@ $(addprefix $(BUILDDIR)/,threading.o threading.dbg.obj gc.o gc.dbg.obj init.c in $(addprefix $(BUILDDIR)/,APInt-C.o APInt-C.dbg.obj runtime_intrinsics.o runtime_intrinsics.dbg.obj): $(SRCDIR)/APInt-C.h # archive library file rules -$(BUILDDIR)/support/libsupport.a: $(addprefix $(SRCDIR)/support/,*.h *.c *.S) $(SRCDIR)/support/*.c +$(BUILDDIR)/support/libsupport.a: $(addprefix $(SRCDIR)/support/,*.h *.c *.S *.inc) $(SRCDIR)/support/*.c $(MAKE) -C $(SRCDIR)/support BUILDDIR='$(abspath $(BUILDDIR)/support)' -$(BUILDDIR)/support/libsupport-debug.a: $(addprefix $(SRCDIR)/support/,*.h *.c *.S) $(SRCDIR)/support/*.c +$(BUILDDIR)/support/libsupport-debug.a: $(addprefix $(SRCDIR)/support/,*.h *.c *.S *.inc) $(SRCDIR)/support/*.c $(MAKE) -C $(SRCDIR)/support debug BUILDDIR='$(abspath $(BUILDDIR)/support)' $(FLISP_EXECUTABLE_release): $(BUILDDIR)/flisp/libflisp.a diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 93683d320e6b9..11e35764190cc 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -286,8 +286,8 @@ void *jl_create_native(jl_array_t *methods, const jl_cgparams_t cgparams, int _p JL_GC_PUSH1(&src); JL_LOCK(&codegen_lock); uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); CompilationPolicy policy = (CompilationPolicy) _policy; @@ -415,8 +415,8 @@ void *jl_create_native(jl_array_t *methods, const jl_cgparams_t cgparams, int _p } data->M = std::move(clone); - if (jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); if (policy == CompilationPolicy::ImagingMode) imaging_mode = 0; JL_UNLOCK(&codegen_lock); // Might GC @@ -916,8 +916,8 @@ void *jl_get_llvmf_defn(jl_method_instance_t *mi, size_t world, char getwrapper, jl_llvm_functions_t decls; JL_LOCK(&codegen_lock); uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); std::tie(m, decls) = jl_emit_code(mi, src, jlrettype, output); @@ -942,8 +942,8 @@ void *jl_get_llvmf_defn(jl_method_instance_t *mi, size_t world, char getwrapper, m.release(); // the return object `llvmf` will be the owning pointer } JL_GC_POP(); - if (jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&codegen_lock); // Might GC if (F) return F; diff --git a/src/ast.c b/src/ast.c index 2a0661e629564..de2492db08c94 100644 --- a/src/ast.c +++ b/src/ast.c @@ -28,6 +28,7 @@ extern "C" { // head symbols for each expression type jl_sym_t *call_sym; jl_sym_t *invoke_sym; +jl_sym_t *invoke_modify_sym; jl_sym_t *empty_sym; jl_sym_t *top_sym; jl_sym_t *module_sym; jl_sym_t *slot_sym; jl_sym_t *export_sym; jl_sym_t *import_sym; @@ -345,6 +346,7 @@ void jl_init_common_symbols(void) empty_sym = jl_symbol(""); call_sym = jl_symbol("call"); invoke_sym = jl_symbol("invoke"); + invoke_modify_sym = jl_symbol("invoke_modify"); foreigncall_sym = jl_symbol("foreigncall"); cfunction_sym = jl_symbol("cfunction"); quote_sym = jl_symbol("quote"); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 4ae6c4b21594a..aec9b59c98bda 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1542,12 +1542,29 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j } static jl_cgval_t typed_store(jl_codectx_t &ctx, - Value *ptr, Value *idx_0based, const jl_cgval_t &rhs, const jl_cgval_t &cmp, + Value *ptr, Value *idx_0based, jl_cgval_t rhs, jl_cgval_t cmp, jl_value_t *jltype, MDNode *tbaa, MDNode *aliasscope, Value *parent, // for the write barrier, NULL if no barrier needed bool isboxed, AtomicOrdering Order, AtomicOrdering FailOrder, unsigned alignment, - bool needlock, bool issetfield, bool isreplacefield, bool maybe_null_if_boxed) + bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, + bool maybe_null_if_boxed, const jl_cgval_t *modifyop, const std::string &fname) { + auto newval = [&](const jl_cgval_t &lhs) { + const jl_cgval_t argv[3] = { cmp, lhs, rhs }; + jl_cgval_t ret; + if (modifyop) { + ret = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); + } + else { + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, JLCALL_F_CC); + ret = mark_julia_type(ctx, callval, true, jl_any_type); + } + if (!jl_subtype(ret.typ, jltype)) { + emit_typecheck(ctx, ret, jltype, fname + "typed_store"); + ret = update_julia_type(ctx, ret, jltype); + } + return ret; + }; assert(!needlock || parent != nullptr); Type *elty = isboxed ? T_prjlvalue : julia_type_to_llvm(ctx, jltype); if (type_is_ghost(elty)) { @@ -1559,14 +1576,19 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else if (isreplacefield) { Value *Success = emit_f_is(ctx, cmp, ghostValue(jltype)); Success = ctx.builder.CreateZExt(Success, T_int8); - jl_cgval_t argv[2] = {ghostValue(jltype), mark_julia_type(ctx, Success, false, jl_bool_type)}; - // TODO: do better here - Value *instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - return mark_julia_type(ctx, instr, true, jl_any_type); + const jl_cgval_t argv[2] = {ghostValue(jltype), mark_julia_type(ctx, Success, false, jl_bool_type)}; + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jltype); + return emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } - else { + else if (isswapfield) { return ghostValue(jltype); } + else { // modifyfield + jl_cgval_t oldval = ghostValue(jltype); + const jl_cgval_t argv[2] = { oldval, newval(oldval) }; + jl_datatype_t *rettyp = jl_apply_modify_type(jltype); + return emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); + } } Value *intcast = nullptr; if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy()) { @@ -1583,13 +1605,15 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, if (nb != nb2) elty = Type::getIntNTy(jl_LLVMContext, nb2); } - Value *r; - if (!isboxed) - r = emit_unbox(ctx, realelty, rhs, jltype); - else - r = boxed(ctx, rhs); - if (realelty != elty) - r = ctx.builder.CreateZExt(r, elty); + Value *r = nullptr; + if (issetfield || isswapfield || isreplacefield) { + if (!isboxed) + r = emit_unbox(ctx, realelty, rhs, jltype); + else + r = boxed(ctx, rhs); + if (realelty != elty) + r = ctx.builder.CreateZExt(r, elty); + } Type *ptrty = PointerType::get(elty, ptr->getType()->getPointerAddressSpace()); if (ptr->getType() != ptrty) ptr = ctx.builder.CreateBitCast(ptr, ptrty); @@ -1599,33 +1623,22 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, alignment = sizeof(void*); else if (!alignment) alignment = julia_alignment(jltype); - Instruction *instr = nullptr; + Value *instr = nullptr; Value *Compare = nullptr; Value *Success = nullptr; - BasicBlock *DoneBB = issetfield || (!isreplacefield && !isboxed) ? nullptr : BasicBlock::Create(jl_LLVMContext, "done_xchg", ctx.f); + BasicBlock *DoneBB = nullptr; if (needlock) emit_lockstate_value(ctx, parent, true); jl_cgval_t oldval = rhs; - if (issetfield || Order == AtomicOrdering::NotAtomic) { - if (!issetfield) { - instr = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); + if (issetfield || (Order == AtomicOrdering::NotAtomic && isswapfield)) { + if (isswapfield) { + auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); if (aliasscope) - instr->setMetadata("noalias", aliasscope); + load->setMetadata("noalias", aliasscope); if (tbaa) - tbaa_decorate(tbaa, instr); + tbaa_decorate(tbaa, load); assert(realelty == elty); - if (isreplacefield) { - oldval = mark_julia_type(ctx, instr, isboxed, jltype); - Value *first_ptr = nullptr; - if (maybe_null_if_boxed) - first_ptr = isboxed ? instr : extract_first_ptr(ctx, instr); - Success = emit_nullcheck_guard(ctx, first_ptr, [&] { - return emit_f_is(ctx, oldval, cmp); - }); - BasicBlock *BB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); - ctx.builder.CreateCondBr(Success, BB, DoneBB); - ctx.builder.SetInsertPoint(BB); - } + instr = load; } StoreInst *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); store->setOrdering(Order); @@ -1633,20 +1646,37 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, store->setMetadata("noalias", aliasscope); if (tbaa) tbaa_decorate(tbaa, store); - if (DoneBB) - ctx.builder.CreateBr(DoneBB); } - else if (isboxed || isreplacefield) { - // we have to handle isboxed here as a workaround for really bad LLVM design issue: plain Xchg only works with integers + else if (isswapfield && !isboxed) { + // we can't handle isboxed here as a workaround for really bad LLVM + // design issue: plain Xchg only works with integers +#if JL_LLVM_VERSION >= 130000 + auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Align(alignment), Order); +#else + auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Order); + store->setAlignment(Align(alignment)); +#endif + if (aliasscope) + store->setMetadata("noalias", aliasscope); + if (tbaa) + tbaa_decorate(tbaa, store); + instr = store; + } + else { + // replacefield, modifyfield, or swapfield (isboxed && atomic) + DoneBB = BasicBlock::Create(jl_LLVMContext, "done_xchg", ctx.f); bool needloop; PHINode *Succ = nullptr, *Current = nullptr; if (isreplacefield) { - if (!isboxed) { + if (Order == AtomicOrdering::NotAtomic) { + needloop = false; + } + else if (!isboxed) { needloop = ((jl_datatype_t*)jltype)->layout->haspadding; Value *SameType = emit_isa(ctx, cmp, jltype, nullptr).first; if (SameType != ConstantInt::getTrue(jl_LLVMContext)) { BasicBlock *SkipBB = BasicBlock::Create(jl_LLVMContext, "skip_xchg", ctx.f); - BasicBlock *BB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); + BasicBlock *BB = BasicBlock::Create(jl_LLVMContext, "ok_xchg", ctx.f); ctx.builder.CreateCondBr(SameType, BB, SkipBB); ctx.builder.SetInsertPoint(SkipBB); LoadInst *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); @@ -1659,7 +1689,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); Succ = ctx.builder.CreatePHI(T_int1, 2); - Succ->addIncoming(ConstantInt::get(T_int1, 0), SkipBB); + Succ->addIncoming(ConstantInt::get(T_int1, false), SkipBB); Current = ctx.builder.CreatePHI(instr->getType(), 2); Current->addIncoming(instr, SkipBB); ctx.builder.SetInsertPoint(BB); @@ -1677,50 +1707,112 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, needloop = true; } } - else { + else { // swap or modify LoadInst *Current = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - Current->setOrdering(AtomicOrdering::Monotonic); + Current->setOrdering(Order == AtomicOrdering::NotAtomic ? Order : AtomicOrdering::Monotonic); if (aliasscope) Current->setMetadata("noalias", aliasscope); if (tbaa) tbaa_decorate(tbaa, Current); Compare = Current; - needloop = true; + needloop = !isswapfield || Order != AtomicOrdering::NotAtomic; } BasicBlock *BB; + PHINode *CmpPhi; if (needloop) { BasicBlock *From = ctx.builder.GetInsertBlock(); BB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); ctx.builder.CreateBr(BB); ctx.builder.SetInsertPoint(BB); - PHINode *Cmp = ctx.builder.CreatePHI(r->getType(), 2); - Cmp->addIncoming(Compare, From); - Compare = Cmp; - } - if (Order == AtomicOrdering::Unordered) - Order = AtomicOrdering::Monotonic; - if (!isreplacefield) - FailOrder = AtomicOrdering::Monotonic; - else if (FailOrder == AtomicOrdering::Unordered) - FailOrder = AtomicOrdering::Monotonic; + CmpPhi = ctx.builder.CreatePHI(elty, 2); + CmpPhi->addIncoming(Compare, From); + Compare = CmpPhi; + } + if (ismodifyfield) { + if (needlock) + emit_lockstate_value(ctx, parent, false); + Value *realCompare = Compare; + if (realelty != elty) + realCompare = ctx.builder.CreateTrunc(realCompare, realelty); + if (intcast) { + ctx.builder.CreateStore(realCompare, ctx.builder.CreateBitCast(intcast, realCompare->getType()->getPointerTo())); + if (maybe_null_if_boxed) + realCompare = ctx.builder.CreateLoad(intcast); + } + if (maybe_null_if_boxed) { + Value *first_ptr = isboxed ? Compare : extract_first_ptr(ctx, Compare); + if (first_ptr) + null_pointer_check(ctx, first_ptr, nullptr); + } + if (intcast) + oldval = mark_julia_slot(intcast, jltype, NULL, tbaa_stack); + else + oldval = mark_julia_type(ctx, realCompare, isboxed, jltype); + rhs = newval(oldval); + if (!isboxed) + r = emit_unbox(ctx, realelty, rhs, jltype); + else + r = boxed(ctx, rhs); + if (realelty != elty) + r = ctx.builder.CreateZExt(r, elty); + if (needlock) + emit_lockstate_value(ctx, parent, true); + cmp = oldval; + } + Value *Done; + if (Order == AtomicOrdering::NotAtomic) { + // modifyfield or replacefield + assert(elty == realelty && !intcast); + auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); + if (aliasscope) + load->setMetadata("noalias", aliasscope); + if (tbaa) + tbaa_decorate(tbaa, load); + Value *first_ptr = nullptr; + if (maybe_null_if_boxed && !ismodifyfield) + first_ptr = isboxed ? load : extract_first_ptr(ctx, load); + oldval = mark_julia_type(ctx, load, isboxed, jltype); + Success = emit_nullcheck_guard(ctx, first_ptr, [&] { + return emit_f_is(ctx, oldval, cmp); + }); + if (needloop && ismodifyfield) + CmpPhi->addIncoming(load, ctx.builder.GetInsertBlock()); + assert(Succ == nullptr); + BasicBlock *XchgBB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); + ctx.builder.CreateCondBr(Success, XchgBB, needloop && ismodifyfield ? BB : DoneBB); + ctx.builder.SetInsertPoint(XchgBB); + auto *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); + if (aliasscope) + store->setMetadata("noalias", aliasscope); + if (tbaa) + tbaa_decorate(tbaa, store); + ctx.builder.CreateBr(DoneBB); + instr = load; + } + else { + if (Order == AtomicOrdering::Unordered) + Order = AtomicOrdering::Monotonic; + if (!isreplacefield) + FailOrder = AtomicOrdering::Monotonic; + else if (FailOrder == AtomicOrdering::Unordered) + FailOrder = AtomicOrdering::Monotonic; #if JL_LLVM_VERSION >= 130000 - auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Align(alignment), Order, FailOrder); + auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Align(alignment), Order, FailOrder); #else - auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Order, FailOrder); - store->setAlignment(Align(alignment)); + auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Order, FailOrder); + store->setAlignment(Align(alignment)); #endif - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); - instr = ctx.builder.Insert(ExtractValueInst::Create(store, 0)); - Success = ctx.builder.Insert(ExtractValueInst::Create(store, 1)); - Value *Done = Success; - if (needloop) { - if (isreplacefield) { + if (aliasscope) + store->setMetadata("noalias", aliasscope); + if (tbaa) + tbaa_decorate(tbaa, store); + instr = ctx.builder.Insert(ExtractValueInst::Create(store, 0)); + Success = ctx.builder.Insert(ExtractValueInst::Create(store, 1)); + Done = Success; + if (isreplacefield && needloop) { Value *realinstr = instr; if (realelty != elty) - realinstr = ctx.builder.CreateTrunc(instr, realelty); + realinstr = ctx.builder.CreateTrunc(realinstr, realelty); if (intcast) { ctx.builder.CreateStore(realinstr, ctx.builder.CreateBitCast(intcast, realinstr->getType()->getPointerTo())); oldval = mark_julia_slot(intcast, jltype, NULL, tbaa_stack); @@ -1740,7 +1832,12 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, }); Done = ctx.builder.CreateNot(Done); } - cast(Compare)->addIncoming(instr, ctx.builder.GetInsertBlock()); + if (needloop) + ctx.builder.CreateCondBr(Done, DoneBB, BB); + else + ctx.builder.CreateBr(DoneBB); + if (needloop) + CmpPhi->addIncoming(instr, ctx.builder.GetInsertBlock()); } if (Succ != nullptr) { Current->addIncoming(instr, ctx.builder.GetInsertBlock()); @@ -1748,31 +1845,12 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, Succ->addIncoming(Success, ctx.builder.GetInsertBlock()); Success = Succ; } - if (needloop) - ctx.builder.CreateCondBr(Done, DoneBB, BB); - else - ctx.builder.CreateBr(DoneBB); - } - else { -#if JL_LLVM_VERSION >= 130000 - instr = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Align(alignment), Order); -#else - auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Order); - store->setAlignment(Align(alignment)); - instr = store; -#endif - if (aliasscope) - instr->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, instr); - assert(DoneBB == nullptr); } if (DoneBB) ctx.builder.SetInsertPoint(DoneBB); if (needlock) emit_lockstate_value(ctx, parent, false); if (parent != NULL) { - BasicBlock *DoneBB; if (isreplacefield) { // TOOD: avoid this branch if we aren't making a write barrier BasicBlock *BB = BasicBlock::Create(jl_LLVMContext, "xchg_wb", ctx.f); @@ -1789,7 +1867,12 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.SetInsertPoint(DoneBB); } } - if (!issetfield) { + if (ismodifyfield) { + const jl_cgval_t argv[2] = { oldval, rhs }; + jl_datatype_t *rettyp = jl_apply_modify_type(jltype); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); + } + else if (!issetfield) { // swapfield or replacefield if (realelty != elty) instr = ctx.builder.Insert(CastInst::Create(Instruction::Trunc, instr, realelty)); if (intcast) { @@ -1803,10 +1886,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, } oldval = mark_julia_type(ctx, instr, isboxed, jltype); if (isreplacefield) { - // TODO: do better here - jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; - instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - oldval = mark_julia_type(ctx, instr, true, jl_any_type); + Success = ctx.builder.CreateZExt(Success, T_int8); + const jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jltype); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } } return oldval; @@ -3189,12 +3272,13 @@ static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg static jl_cgval_t emit_setfield(jl_codectx_t &ctx, jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, - const jl_cgval_t &rhs, const jl_cgval_t &cmp, + jl_cgval_t rhs, jl_cgval_t cmp, bool checked, bool wb, AtomicOrdering Order, AtomicOrdering FailOrder, - bool needlock, bool issetfield, bool isreplacefield) + bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, + const jl_cgval_t *modifyop, const std::string &fname) { if (!sty->name->mutabl && checked) { - std::string msg = "setfield!: immutable struct of type " + std::string msg = fname + "immutable struct of type " + std::string(jl_symbol_name(sty->name->name)) + " cannot be changed"; emit_error(ctx, msg); @@ -3218,39 +3302,68 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, jl_cgval_t rhs_union = convert_julia_type(ctx, rhs, jfty); if (rhs_union.typ == jl_bottom_type) return jl_cgval_t(); - Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jfty); - tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(T_int8, 1)); Value *ptindex = ctx.builder.CreateInBoundsGEP(T_int8, emit_bitcast(ctx, maybe_decay_tracked(ctx, addr), T_pint8), ConstantInt::get(T_size, fsz)); if (needlock) emit_lockstate_value(ctx, strct, true); + BasicBlock *BB = ctx.builder.GetInsertBlock(); jl_cgval_t oldval = rhs; if (!issetfield) oldval = emit_unionload(ctx, addr, ptindex, jfty, fsz, al, strct.tbaa, true); Value *Success; BasicBlock *DoneBB; - if (isreplacefield) { - BasicBlock *BB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); + if (isreplacefield || ismodifyfield) { + if (ismodifyfield) { + if (needlock) + emit_lockstate_value(ctx, strct, false); + const jl_cgval_t argv[3] = { cmp, oldval, rhs }; + if (modifyop) { + rhs = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); + } + else { + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, JLCALL_F_CC); + rhs = mark_julia_type(ctx, callval, true, jl_any_type); + } + if (!jl_subtype(rhs.typ, jfty)) { + emit_typecheck(ctx, rhs, jfty, fname); + rhs = update_julia_type(ctx, rhs, jfty); + } + rhs_union = convert_julia_type(ctx, rhs, jfty); + if (rhs_union.typ == jl_bottom_type) + return jl_cgval_t(); + if (needlock) + emit_lockstate_value(ctx, strct, true); + cmp = oldval; + oldval = emit_unionload(ctx, addr, ptindex, jfty, fsz, al, strct.tbaa, true); + } + BasicBlock *XchgBB = BasicBlock::Create(jl_LLVMContext, "xchg", ctx.f); DoneBB = BasicBlock::Create(jl_LLVMContext, "done_xchg", ctx.f); Success = emit_f_is(ctx, oldval, cmp); - ctx.builder.CreateCondBr(Success, BB, DoneBB); - ctx.builder.SetInsertPoint(BB); + ctx.builder.CreateCondBr(Success, XchgBB, ismodifyfield ? BB : DoneBB); + ctx.builder.SetInsertPoint(XchgBB); } + Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jfty); + tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(T_int8, 1)); tbaa_decorate(tbaa_unionselbyte, ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); // copy data if (!rhs.isghost) { emit_unionmove(ctx, addr, strct.tbaa, rhs, nullptr); } - if (isreplacefield) { + if (isreplacefield || ismodifyfield) { ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); } if (needlock) emit_lockstate_value(ctx, strct, false); if (isreplacefield) { + Success = ctx.builder.CreateZExt(Success, T_int8); jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; - // TODO: do better here - Value *instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - oldval = mark_julia_type(ctx, instr, true, jl_any_type); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jfty); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); + } + else if (ismodifyfield) { + jl_cgval_t argv[2] = {oldval, rhs}; + jl_datatype_t *rettyp = jl_apply_modify_type(jfty); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } return oldval; } @@ -3262,7 +3375,7 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, return typed_store(ctx, addr, NULL, rhs, cmp, jfty, strct.tbaa, nullptr, wb ? maybe_bitcast(ctx, data_pointer(ctx, strct), T_pjlvalue) : nullptr, isboxed, Order, FailOrder, align, - needlock, issetfield, isreplacefield, maybe_null); + needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, maybe_null, modifyop, fname); } } @@ -3441,7 +3554,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else need_wb = false; emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); - emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), false, need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false); + emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), false, need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, ""); } return strctinfo; } diff --git a/src/codegen.cpp b/src/codegen.cpp index e6505f7b67870..8db4a7503b116 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1161,11 +1161,13 @@ static Value *get_current_ptls(jl_codectx_t &ctx); static Value *get_current_signal_page(jl_codectx_t &ctx); static void CreateTrap(IRBuilder<> &irbuilder); static CallInst *emit_jlcall(jl_codectx_t &ctx, Function *theFptr, Value *theF, - jl_cgval_t *args, size_t nargs, CallingConv::ID cc); + const jl_cgval_t *args, size_t nargs, CallingConv::ID cc); static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction *theFptr, Value *theF, - jl_cgval_t *args, size_t nargs, CallingConv::ID cc); + const jl_cgval_t *args, size_t nargs, CallingConv::ID cc); static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1 = nullptr, Value *nullcheck2 = nullptr); +static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv); +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt); static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); @@ -2659,6 +2661,102 @@ 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_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) +{ + bool issetfield = f == jl_builtin_setfield; + bool isreplacefield = f == jl_builtin_replacefield; + bool isswapfield = f == jl_builtin_swapfield; + bool ismodifyfield = f == jl_builtin_modifyfield; + const jl_cgval_t undefval; + const jl_cgval_t &obj = argv[1]; + const jl_cgval_t &fld = argv[2]; + jl_cgval_t val = argv[isreplacefield || ismodifyfield ? 4 : 3]; + const jl_cgval_t &cmp = isreplacefield || ismodifyfield ? argv[3] : undefval; + enum jl_memory_order order = jl_memory_order_notatomic; + const std::string fname = issetfield ? "setfield!" : isreplacefield ? "replacefield!" : isswapfield ? "swapfield!" : "modifyfield!"; + if (nargs >= (isreplacefield || ismodifyfield ? 5 : 4)) { + const jl_cgval_t &ord = argv[isreplacefield || ismodifyfield ? 5 : 4]; + emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, fname); + if (!ord.constant) + return false; + order = jl_get_atomic_order((jl_sym_t*)ord.constant, !issetfield, true); + } + enum jl_memory_order fail_order = order; + if (isreplacefield && nargs == 6) { + const jl_cgval_t &ord = argv[6]; + emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, fname); + if (!ord.constant) + return false; + fail_order = jl_get_atomic_order((jl_sym_t*)ord.constant, true, false); + } + if (order == jl_memory_order_invalid || fail_order == jl_memory_order_invalid || fail_order > order) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable + return true; + } + + 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) { + idx = jl_field_index(uty, (jl_sym_t*)fld.constant, 0); + } + else if (fld.constant && fld.typ == (jl_value_t*)jl_long_type) { + ssize_t i = jl_unbox_long(fld.constant); + if (i > 0 && i <= jl_datatype_nfields(uty)) + idx = i - 1; + } + if (idx != -1) { + jl_value_t *ft = jl_svecref(uty->types, idx); + if (!jl_has_free_typevars(ft)) { + if (!ismodifyfield && !jl_subtype(val.typ, ft)) { + emit_typecheck(ctx, val, ft, fname); + val = update_julia_type(ctx, val, ft); + } + // TODO: attempt better codegen for approximate types + bool isboxed = jl_field_isptr(uty, idx); + bool isatomic = jl_field_isatomic(uty, idx); + bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE; + if (isatomic == (order == jl_memory_order_notatomic)) { + emit_atomic_error(ctx, + issetfield ? + (isatomic ? "setfield!: atomic field cannot be written non-atomically" + : "setfield!: non-atomic field cannot be written atomically") : + isreplacefield ? + (isatomic ? "replacefield!: atomic field cannot be written non-atomically" + : "replacefield!: non-atomic field cannot be written atomically") : + isswapfield ? + (isatomic ? "swapfield!: atomic field cannot be written non-atomically" + : "swapfield!: non-atomic field cannot be written atomically") : + (isatomic ? "modifyfield!: atomic field cannot be written non-atomically" + : "modifyfield!: non-atomic field cannot be written atomically")); + *ret = jl_cgval_t(); + return true; + } + if (isatomic == (fail_order == jl_memory_order_notatomic)) { + emit_atomic_error(ctx, + (isatomic ? "replacefield!: atomic field cannot be accessed non-atomically" + : "replacefield!: non-atomic field cannot be accessed atomically")); + *ret = jl_cgval_t(); + return true; + } + *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true, + (needlock || order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + : get_llvm_atomic_order(order), + (needlock || fail_order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + : get_llvm_atomic_order(fail_order), + needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, + modifyop, fname); + return true; + } + } + } + return false; +} + static std::pair, jl_llvm_functions_t> emit_function( jl_method_instance_t *lam, @@ -2985,7 +3083,11 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, false, true, false, - false); + false, + false, + false, + nullptr, + ""); } } *ret = ary; @@ -3127,91 +3229,9 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, 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))) { - bool issetfield = f == jl_builtin_setfield; - bool isreplacefield = f == jl_builtin_replacefield; - const jl_cgval_t undefval; - const jl_cgval_t &obj = argv[1]; - const jl_cgval_t &fld = argv[2]; - jl_cgval_t val = argv[isreplacefield ? 4 : 3]; - const jl_cgval_t &cmp = isreplacefield ? argv[3] : undefval; - enum jl_memory_order order = jl_memory_order_notatomic; - const std::string fname = issetfield ? "setfield!" : isreplacefield ? "replacefield!" : "swapfield!"; - if (nargs >= (isreplacefield ? 5 : 4)) { - const jl_cgval_t &ord = argv[isreplacefield ? 5 : 4]; - emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, fname); - if (!ord.constant) - return false; - order = jl_get_atomic_order((jl_sym_t*)ord.constant, !issetfield, true); - } - enum jl_memory_order fail_order = order; - if (isreplacefield && nargs == 6) { - const jl_cgval_t &ord = argv[6]; - emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, fname); - if (!ord.constant) - return false; - fail_order = jl_get_atomic_order((jl_sym_t*)ord.constant, true, false); - } - if (order == jl_memory_order_invalid || fail_order == jl_memory_order_invalid || fail_order > order) { - emit_atomic_error(ctx, "invalid atomic ordering"); - *ret = jl_cgval_t(); // unreachable - return true; - } - - 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) { - idx = jl_field_index(uty, (jl_sym_t*)fld.constant, 0); - } - else if (fld.constant && fld.typ == (jl_value_t*)jl_long_type) { - ssize_t i = jl_unbox_long(fld.constant); - if (i > 0 && i <= jl_datatype_nfields(uty)) - idx = i - 1; - } - if (idx != -1) { - jl_value_t *ft = jl_svecref(uty->types, idx); - if (!jl_has_free_typevars(ft)) { - if (!jl_subtype(val.typ, ft)) { - emit_typecheck(ctx, val, ft, fname); - val = update_julia_type(ctx, val, ft); - } - // TODO: attempt better codegen for approximate types - bool isboxed = jl_field_isptr(uty, idx); - bool isatomic = jl_field_isatomic(uty, idx); - bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE; - if (isatomic == (order == jl_memory_order_notatomic)) { - emit_atomic_error(ctx, - issetfield ? - (isatomic ? "setfield!: atomic field cannot be written non-atomically" - : "setfield!: non-atomic field cannot be written atomically") : - isreplacefield ? - (isatomic ? "replacefield!: atomic field cannot be written non-atomically" - : "replacefield!: non-atomic field cannot be written atomically") : - (isatomic ? "swapfield!: atomic field cannot be written non-atomically" - : "swapfield!: non-atomic field cannot be written atomically")); - *ret = jl_cgval_t(); - return true; - } - if (isatomic == (fail_order == jl_memory_order_notatomic)) { - emit_atomic_error(ctx, - (isatomic ? "replacefield!: atomic field cannot be accessed non-atomically" - : "replacefield!: non-atomic field cannot be accessed atomically")); - *ret = jl_cgval_t(); - return true; - } - *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true, - (needlock || order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(order), - (needlock || fail_order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(fail_order), - needlock, issetfield, isreplacefield); - return true; - } - } - } + (f == jl_builtin_replacefield && (nargs == 4 || nargs == 5 || nargs == 6)) || + (f == jl_builtin_modifyfield && (nargs == 4 || nargs == 5))) { + return emit_f_opfield(ctx, ret, f, argv, nargs, nullptr); } else if (f == jl_builtin_nfields && nargs == 1) { @@ -3438,7 +3458,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // Returns T_prjlvalue static CallInst *emit_jlcall(jl_codectx_t &ctx, Function *theFptr, Value *theF, - jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) + const jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) { // emit arguments SmallVector theArgs; @@ -3462,14 +3482,14 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, Function *theFptr, Value *theF, } // Returns T_prjlvalue static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction *theFptr, Value *theF, - jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) + const jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) { return emit_jlcall(ctx, prepare_call(theFptr), theF, argv, nargs, cc); } static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_t *mi, jl_value_t *jlretty, StringRef specFunctionObject, - jl_cgval_t *argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) + const jl_cgval_t *argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) { // emit specialized call site bool is_opaque_closure = jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure; @@ -3549,7 +3569,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ jl_cgval_t retval; switch (returninfo.cc) { case jl_returninfo_t::Boxed: - retval = mark_julia_type(ctx, call, true, inferred_retty); + retval = mark_julia_type(ctx, call, true, jlretty); break; case jl_returninfo_t::Register: retval = mark_julia_type(ctx, call, false, jlretty); @@ -3579,20 +3599,18 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ break; } // see if inference has a different / better type for the call than the lambda - if (inferred_retty != retval.typ) - retval = update_julia_type(ctx, retval, inferred_retty); - return retval; + return update_julia_type(ctx, retval, inferred_retty); } -static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, StringRef specFunctionObject, - jl_cgval_t *argv, size_t nargs, jl_value_t *inferred_retty) +static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty, StringRef specFunctionObject, + const jl_cgval_t *argv, size_t nargs, jl_value_t *inferred_retty) { auto theFptr = cast( jl_Module->getOrInsertFunction(specFunctionObject, jl_func_sig).getCallee()); add_return_attr(theFptr, Attribute::NonNull); theFptr->addFnAttr(Thunk); Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, JLCALL_F_CC); - return mark_julia_type(ctx, ret, true, inferred_retty); + return update_julia_type(ctx, mark_julia_type(ctx, ret, true, jlretty), inferred_retty); } static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) @@ -3609,7 +3627,11 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) if (argv[i].typ == jl_bottom_type) return jl_cgval_t(); } + return emit_invoke(ctx, lival, argv, nargs, rt); +} +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_cgval_t *argv, size_t nargs, jl_value_t *rt) +{ bool handled = false; jl_cgval_t result; if (lival.constant) { @@ -3621,7 +3643,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) FunctionType *ft = ctx.f->getFunctionType(); StringRef protoname = ctx.f->getName(); if (ft == jl_func_sig) { - result = emit_call_specfun_boxed(ctx, protoname, argv, nargs, rt); + result = emit_call_specfun_boxed(ctx, ctx.rettype, protoname, argv, nargs, rt); handled = true; } else if (ft != jl_func_sig_sparams) { @@ -3663,7 +3685,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) if (specsig) result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, argv, nargs, &cc, &return_roots, rt); else - result = emit_call_specfun_boxed(ctx, protoname, argv, nargs, rt); + result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, argv, nargs, rt); handled = true; if (need_to_emit) { Function *trampoline_decl = cast(jl_Module->getNamedValue(protoname)); @@ -3682,6 +3704,40 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) return result; } +static jl_cgval_t emit_invoke_modify(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) +{ + jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); + size_t arglen = jl_array_dim0(ex->args); + size_t nargs = arglen - 1; + assert(arglen >= 2); + jl_cgval_t lival = emit_expr(ctx, args[0]); + jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + for (size_t i = 0; i < nargs; ++i) { + argv[i] = emit_expr(ctx, args[i + 1]); + if (argv[i].typ == jl_bottom_type) + return jl_cgval_t(); + } + const jl_cgval_t &f = argv[0]; + jl_cgval_t ret; + if (f.constant && f.constant == jl_builtin_modifyfield) { + if (emit_f_opfield(ctx, &ret, jl_builtin_modifyfield, argv, nargs - 1, &lival)) + return ret; + auto it = builtin_func_map.find(&jl_f_modifyfield); + assert(it != builtin_func_map.end()); + Value *oldnew = emit_jlcall(ctx, it->second, V_rnull, &argv[1], nargs - 1, JLCALL_F_CC); + return mark_julia_type(ctx, oldnew, true, rt); + } + if (f.constant && jl_typeis(f.constant, jl_intrinsic_type)) { + JL_I::intrinsic fi = (intrinsic)*(uint32_t*)jl_data_ptr(f.constant); + if (fi == JL_I::atomic_pointermodify && jl_intrinsic_nargs((int)fi) == nargs - 1) + return emit_atomic_pointerop(ctx, fi, argv, nargs - 1, &lival); + } + + // emit function and arguments + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs, JLCALL_F_CC); + return mark_julia_type(ctx, callval, true, rt); +} + static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) { jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); @@ -4528,6 +4584,12 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaval); return emit_invoke(ctx, ex, expr_t); } + else if (head == invoke_modify_sym) { + assert(ssaval >= 0); + jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : + jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaval); + return emit_invoke_modify(ctx, ex, expr_t); + } else if (head == call_sym) { jl_value_t *expr_t; if (ssaval < 0) @@ -5160,9 +5222,48 @@ static Function* gen_cfun_wrapper( // add nest parameter (pointer to jl_value_t* data array) after sret arg assert(closure_types); std::vector fargt_sig(sig.fargt_sig); + fargt_sig.insert(fargt_sig.begin() + sig.sret, T_pprjlvalue); + + // Shift LLVM attributes for parameters one to the right, as + // we are adding the extra nest parameter after sret arg. + std::vector> newAttributes; + newAttributes.reserve(attributes.getNumAttrSets() + 1); + auto it = attributes.index_begin(); + + // Skip past FunctionIndex + if (it == AttributeList::AttrIndex::FunctionIndex) { + ++it; + } + + // Move past ReturnValue and parameter return value + for (;it < AttributeList::AttrIndex::FirstArgIndex + sig.sret; ++it) { + if (attributes.hasAttributes(it)) { + newAttributes.emplace_back(it, attributes.getAttributes(it)); + } + } + + // Add the new nest attribute + AttrBuilder attrBuilder; + attrBuilder.addAttribute(Attribute::Nest); + newAttributes.emplace_back(it, AttributeSet::get(jl_LLVMContext, attrBuilder)); + + // Shift forward the rest of the attributes + for(;it < attributes.index_end(); ++it) { + if (attributes.hasAttributes(it)) { + newAttributes.emplace_back(it + 1, attributes.getAttributes(it)); + } + } + + // Remember to add back FunctionIndex + if (attributes.hasAttributes(AttributeList::AttrIndex::FunctionIndex)) { + newAttributes.emplace_back(AttributeList::AttrIndex::FunctionIndex, + attributes.getAttributes(AttributeList::AttrIndex::FunctionIndex)); + } + + // Create the new AttributeList + attributes = AttributeList::get(jl_LLVMContext, newAttributes); functype = FunctionType::get(sig.sret ? T_void : sig.prt, fargt_sig, /*isVa*/false); - attributes = attributes.addAttribute(jl_LLVMContext, 1 + sig.sret, Attribute::Nest); } else { functype = sig.functype(); diff --git a/src/datatype.c b/src/datatype.c index 1a3ffa78170ac..aecbe6f407ae6 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -953,18 +953,13 @@ JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expect return success; } -JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) +JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t *rettyp, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) { // dst must have the required alignment for an atomic of the given size // n.b.: this does not spuriously fail if there are padding bits - jl_value_t *params[2]; - params[0] = (jl_value_t*)dt; - params[1] = (jl_value_t*)jl_bool_type; - jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); - JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) - int isptr = jl_field_isptr(tuptyp, 0); jl_task_t *ct = jl_current_task; - jl_value_t *y = jl_gc_alloc(ct->ptls, isptr ? nb : tuptyp->size, isptr ? dt : tuptyp); + int isptr = jl_field_isptr(rettyp, 0); + jl_value_t *y = jl_gc_alloc(ct->ptls, isptr ? nb : rettyp->size, isptr ? dt : rettyp); int success; jl_datatype_t *et = (jl_datatype_t*)jl_typeof(expected); if (nb == 0) { @@ -1053,7 +1048,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, co } if (isptr) { JL_GC_PUSH1(&y); - jl_value_t *z = jl_gc_alloc(ct->ptls, tuptyp->size, tuptyp); + jl_value_t *z = jl_gc_alloc(ct->ptls, rettyp->size, rettyp); *(jl_value_t**)z = y; JL_GC_POP(); y = z; @@ -1658,8 +1653,11 @@ jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_valu args[0] = r; jl_gc_safepoint(); } - // args[0] == r (old); args[1] == y (new) - args[0] = jl_f_tuple(NULL, args, 2); + // args[0] == r (old) + // args[1] == y (new) + jl_datatype_t *rettyp = jl_apply_modify_type(ty); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + args[0] = jl_new_struct(rettyp, args[0], args[1]); JL_GC_POP(); return args[0]; } @@ -1671,6 +1669,8 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_type_error("replacefield!", ty, rhs); size_t offs = jl_field_offset(st, i); jl_value_t *r = expected; + jl_datatype_t *rettyp = jl_apply_cmpswap_type(ty); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) if (jl_field_isptr(st, i)) { jl_value_t **p = (jl_value_t**)((char*)v + offs); int success; @@ -1683,11 +1683,8 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val if (success || !jl_egal(r, expected)) break; } - jl_value_t **args; - JL_GC_PUSHARGS(args, 2); - args[0] = r; - args[1] = success ? jl_true : jl_false; - r = jl_f_tuple(NULL, args, 2); + JL_GC_PUSH1(&r); + r = jl_new_struct(rettyp, r, success ? jl_true : jl_false); JL_GC_POP(); } else { @@ -1695,7 +1692,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val int isunion = jl_is_uniontype(ty); int needlock; jl_value_t *rty = ty; - size_t fsz; + size_t fsz = jl_field_size(st, i); if (isunion) { assert(!isatomic); hasptr = 0; @@ -1708,7 +1705,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); } if (isatomic && !needlock) { - r = jl_atomic_cmpswap_bits((jl_datatype_t*)rty, (char*)v + offs, r, rhs, fsz); + r = jl_atomic_cmpswap_bits((jl_datatype_t*)ty, rettyp, (char*)v + offs, r, rhs, fsz); int success = *((uint8_t*)r + fsz); if (success && hasptr) jl_gc_multi_wb(v, rhs); // rhs is immutable @@ -1717,23 +1714,17 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_task_t *ct = jl_current_task; uint8_t *psel; if (isunion) { - size_t fsz = jl_field_size(st, i); psel = &((uint8_t*)v)[offs + fsz - 1]; rty = jl_nth_union_component(rty, *psel); } - jl_value_t *params[2]; - params[0] = rty; - params[1] = (jl_value_t*)jl_bool_type; - jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); - JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) - assert(!jl_field_isptr(tuptyp, 0)); - r = jl_gc_alloc(ct->ptls, tuptyp->size, (jl_value_t*)tuptyp); + assert(!jl_field_isptr(rettyp, 0)); + r = jl_gc_alloc(ct->ptls, rettyp->size, (jl_value_t*)rettyp); int success = (rty == jl_typeof(expected)); if (needlock) jl_lock_value(v); - size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy - memcpy((char*)r, (char*)v + offs, fsz); + memcpy((char*)r, (char*)v + offs, fsz); // copy field, including union bits if (success) { + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy if (((jl_datatype_t*)rty)->layout->haspadding) success = jl_egal__bits(r, expected, (jl_datatype_t*)rty); else diff --git a/src/dump.c b/src/dump.c index 49fa6efa431cd..f7a0ced4a6ab6 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2719,7 +2719,7 @@ void jl_init_serializer(void) htable_new(&backref_table, 0); void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, - call_sym, invoke_sym, goto_ifnot_sym, return_sym, jl_symbol("tuple"), + call_sym, invoke_sym, invoke_modify_sym, goto_ifnot_sym, return_sym, jl_symbol("tuple"), jl_an_empty_string, jl_an_empty_vec_any, // empirical list of very common symbols diff --git a/src/gc.c b/src/gc.c index 5429510f08651..8b9688833c5dd 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2778,6 +2778,7 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp } 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 static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) @@ -2809,6 +2810,8 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) // constants gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); + if (cmpswap_names != NULL) + gc_mark_queue_obj(gc_cache, sp, cmpswap_names); } // find unmarked objects that need to be finalized from the finalizer list "list". diff --git a/src/gf.c b/src/gf.c index 41381ccc5178e..118a0a605c7fc 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3160,19 +3160,23 @@ int jl_has_concrete_subtype(jl_value_t *typ) #define typeinf_lock codegen_lock static uint64_t inference_start_time = 0; +static uint8_t inference_is_measuring_compile_time = 0; JL_DLLEXPORT void jl_typeinf_begin(void) { JL_LOCK(&typeinf_lock); - if (jl_measure_compile_time[jl_threadid()]) + if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { inference_start_time = jl_hrtime(); + inference_is_measuring_compile_time = 1; + } } JL_DLLEXPORT void jl_typeinf_end(void) { - int tid = jl_threadid(); - if (typeinf_lock.count == 1 && jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - inference_start_time); + if (typeinf_lock.count == 1 && inference_is_measuring_compile_time) { + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - inference_start_time)); + inference_is_measuring_compile_time = 0; + } JL_UNLOCK(&typeinf_lock); } diff --git a/src/init.c b/src/init.c index 5c0ce45a77912..c5ebd4684205c 100644 --- a/src/init.c +++ b/src/init.c @@ -841,6 +841,7 @@ static void post_boot_hooks(void) jl_methoderror_type = (jl_datatype_t*)core("MethodError"); jl_loaderror_type = (jl_datatype_t*)core("LoadError"); jl_initerror_type = (jl_datatype_t*)core("InitError"); + jl_pair_type = core("Pair"); jl_weakref_type = (jl_datatype_t*)core("WeakRef"); jl_vecelement_typename = ((jl_datatype_t*)jl_unwrap_unionall(core("VecElement")))->name; diff --git a/src/interpreter.c b/src/interpreter.c index 7858bd6ddc4ea..2bfa6482aa9f7 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -79,6 +79,7 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) { jl_value_t **args = jl_array_ptr_data(ex->args); + // generic function definition if (jl_expr_nargs(ex) == 1) { jl_value_t **args = jl_array_ptr_data(ex->args); jl_sym_t *fname = (jl_sym_t*)args[0]; @@ -216,6 +217,9 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) else if (head == invoke_sym) { return do_invoke(args, nargs, s); } + else if (head == invoke_modify_sym) { + return do_call(args + 1, nargs - 1, s); + } else if (head == isdefined_sym) { jl_value_t *sym = args[0]; int defined = 0; diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index e1d821a34e42d..1847fc5c60e37 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -684,7 +684,7 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) if (!type_is_ghost(ptrty)) { thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); typed_store(ctx, thePtr, im1, x, jl_cgval_t(), ety, tbaa_data, nullptr, nullptr, isboxed, - AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, align_nb, false, true, false, false); + AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, align_nb, false, true, false, false, false, false, nullptr, ""); } } return e; @@ -778,15 +778,18 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) // e[i] = x (set) // e[i] <= x (swap) // e[i] y => x (replace) -static jl_cgval_t emit_atomic_pointerset(jl_codectx_t &ctx, intrinsic f, const jl_cgval_t *argv, int nargs) +// x(e[i], y) (modify) +static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl_cgval_t *argv, int nargs, const jl_cgval_t *modifyop) { bool issetfield = f == atomic_pointerset; bool isreplacefield = f == atomic_pointerreplace; + bool isswapfield = f == atomic_pointerswap; + bool ismodifyfield = f == atomic_pointermodify; const jl_cgval_t undefval; const jl_cgval_t &e = argv[0]; - const jl_cgval_t &x = isreplacefield ? argv[2] : argv[1]; - const jl_cgval_t &y = isreplacefield ? argv[1] : undefval; - const jl_cgval_t &ord = isreplacefield ? argv[3] : argv[2]; + const jl_cgval_t &x = isreplacefield || ismodifyfield ? argv[2] : argv[1]; + const jl_cgval_t &y = isreplacefield || ismodifyfield ? argv[1] : undefval; + const jl_cgval_t &ord = isreplacefield || ismodifyfield ? argv[3] : argv[2]; const jl_cgval_t &failord = isreplacefield ? argv[4] : undefval; jl_value_t *aty = e.typ; @@ -814,7 +817,7 @@ static jl_cgval_t emit_atomic_pointerset(jl_codectx_t &ctx, intrinsic f, const j Value *thePtr = emit_unbox(ctx, T_pprjlvalue, e, e.typ); bool isboxed = true; jl_cgval_t ret = typed_store(ctx, thePtr, nullptr, x, y, ety, tbaa_data, nullptr, nullptr, isboxed, - llvm_order, llvm_failorder, sizeof(jl_value_t*), false, issetfield, isreplacefield, false); + llvm_order, llvm_failorder, sizeof(jl_value_t*), false, issetfield, isreplacefield, isswapfield, ismodifyfield, false, modifyop, "atomic_pointermodify"); if (issetfield) ret = e; return ret; @@ -826,7 +829,8 @@ static jl_cgval_t emit_atomic_pointerset(jl_codectx_t &ctx, intrinsic f, const j emit_error(ctx, msg); return jl_cgval_t(); } - emit_typecheck(ctx, x, ety, std::string(jl_intrinsic_name((int)f))); + if (!ismodifyfield) + emit_typecheck(ctx, x, ety, std::string(jl_intrinsic_name((int)f))); size_t nb = jl_datatype_size(ety); if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) { @@ -847,7 +851,7 @@ static jl_cgval_t emit_atomic_pointerset(jl_codectx_t &ctx, intrinsic f, const j assert(!isboxed); Value *thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); jl_cgval_t ret = typed_store(ctx, thePtr, nullptr, x, y, ety, tbaa_data, nullptr, nullptr, isboxed, - llvm_order, llvm_failorder, nb, false, issetfield, isreplacefield, false); + llvm_order, llvm_failorder, nb, false, issetfield, isreplacefield, isswapfield, ismodifyfield, false, modifyop, "atomic_pointermodify"); if (issetfield) ret = e; return ret; @@ -1087,10 +1091,9 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar return emit_atomic_pointerref(ctx, argv); case atomic_pointerset: case atomic_pointerswap: - case atomic_pointerreplace: - return emit_atomic_pointerset(ctx, f, argv, nargs); case atomic_pointermodify: - return emit_runtime_call(ctx, f, argv, nargs); + case atomic_pointerreplace: + return emit_atomic_pointerop(ctx, f, argv, nargs, nullptr); case bitcast: return generic_bitcast(ctx, argv); case trunc_int: diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index e86d6109ff427..a87e14f7a76a4 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -78,16 +78,16 @@ void jl_jit_globals(std::map &globals) extern "C" JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns_before() { - int tid = jl_threadid(); - jl_measure_compile_time[tid] = 1; - return jl_cumulative_compile_time[tid]; + // Increment the flag to allow reentrant callers to `@time`. + jl_atomic_fetch_add(&jl_measure_compile_time_enabled, 1); + return jl_atomic_load_relaxed(&jl_cumulative_compile_time); } extern "C" JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns_after() { - int tid = jl_threadid(); - jl_measure_compile_time[tid] = 0; - return jl_cumulative_compile_time[tid]; + // Decrement the flag when done measuring, allowing other callers to continue measuring. + jl_atomic_fetch_add(&jl_measure_compile_time_enabled, -1); + return jl_atomic_load_relaxed(&jl_cumulative_compile_time); } // this generates llvm code for the lambda info @@ -233,8 +233,8 @@ int jl_compile_extern_c(void *llvmmod, void *p, void *sysimg, jl_value_t *declrt { JL_LOCK(&codegen_lock); uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); jl_codegen_params_t params; jl_codegen_params_t *pparams = (jl_codegen_params_t*)p; @@ -258,8 +258,8 @@ int jl_compile_extern_c(void *llvmmod, void *p, void *sysimg, jl_value_t *declrt if (success && llvmmod == NULL) jl_add_to_ee(std::unique_ptr(into)); } - if (codegen_lock.count == 1 && jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (codegen_lock.count == 1 && measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&codegen_lock); return success; } @@ -315,8 +315,8 @@ jl_code_instance_t *jl_generate_fptr(jl_method_instance_t *mi JL_PROPAGATES_ROOT { JL_LOCK(&codegen_lock); // also disables finalizers, to prevent any unexpected recursion uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); // if we don't have any decls already, try to generate it now jl_code_info_t *src = NULL; @@ -354,8 +354,8 @@ jl_code_instance_t *jl_generate_fptr(jl_method_instance_t *mi JL_PROPAGATES_ROOT else { codeinst = NULL; } - if (codegen_lock.count == 1 && jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (codegen_lock.count == 1 && measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&codegen_lock); JL_GC_POP(); return codeinst; @@ -369,8 +369,8 @@ void jl_generate_fptr_for_unspecialized(jl_code_instance_t *unspec) } JL_LOCK(&codegen_lock); uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); if (unspec->invoke == NULL) { jl_code_info_t *src = NULL; @@ -398,8 +398,8 @@ void jl_generate_fptr_for_unspecialized(jl_code_instance_t *unspec) } JL_GC_POP(); } - if (codegen_lock.count == 1 && jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (codegen_lock.count == 1 && measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&codegen_lock); // Might GC } @@ -422,8 +422,8 @@ jl_value_t *jl_dump_method_asm(jl_method_instance_t *mi, size_t world, // so create an exception here so we can print pretty our lies JL_LOCK(&codegen_lock); // also disables finalizers, to prevent any unexpected recursion uint64_t compiler_start_time = 0; - int tid = jl_threadid(); - if (jl_measure_compile_time[tid]) + uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); + if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); specfptr = (uintptr_t)codeinst->specptr.fptr; if (specfptr == 0) { @@ -448,8 +448,8 @@ jl_value_t *jl_dump_method_asm(jl_method_instance_t *mi, size_t world, } JL_GC_POP(); } - if (jl_measure_compile_time[tid]) - jl_cumulative_compile_time[tid] += (jl_hrtime() - compiler_start_time); + if (measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&codegen_lock); } if (specfptr != 0) diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index bad61d4ade35a..0870464c7d6b5 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -71,6 +71,7 @@ XX(jl_nothing) \ XX(jl_nothing_type) \ XX(jl_number_type) \ + XX(jl_pair_type) \ XX(jl_partial_struct_type) \ XX(jl_partial_opaque_type) \ XX(jl_interconditional_type) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 81e68f3b78ee7..1d2a8a6bed5ac 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -11,6 +11,7 @@ XX(jl_alloc_svec_uninit) \ XX(jl_alloc_vec_any) \ XX(jl_apply_array_type) \ + XX(jl_apply_cmpswap_type) \ XX(jl_apply_generic) \ XX(jl_apply_tuple_type) \ XX(jl_apply_tuple_type_v) \ @@ -542,4 +543,5 @@ XX(jl_wakeup_thread) \ XX(jl_yield) \ XX(jl_print_backtrace) \ - XX(jl_get_pgcstack) + XX(jl_get_pgcstack) \ + diff --git a/src/jltypes.c b/src/jltypes.c index 1ae49c0a32eab..43171ee332e87 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -19,6 +19,8 @@ extern "C" { #endif +jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; + // compute empirical max-probe for a given size #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) #define h2index(hv, sz) (size_t)((hv) & ((sz)-1)) @@ -895,19 +897,19 @@ struct _jl_typestack_t; typedef struct _jl_typestack_t jl_typestack_t; static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value_t **iparams, size_t ntp, - int cacheable, jl_typestack_t *stack, jl_typeenv_t *env); + jl_typestack_t *stack, jl_typeenv_t *env); // Build an environment mapping a TypeName's parameters to parameter values. // This is the environment needed for instantiating a type's supertype and field types. static jl_value_t *inst_datatype_env(jl_value_t *dt, jl_svec_t *p, jl_value_t **iparams, size_t ntp, - int cacheable, jl_typestack_t *stack, jl_typeenv_t *env, int c) + jl_typestack_t *stack, jl_typeenv_t *env, int c) { if (jl_is_datatype(dt)) - return inst_datatype_inner((jl_datatype_t*)dt, p, iparams, ntp, cacheable, stack, env); + return inst_datatype_inner((jl_datatype_t*)dt, p, iparams, ntp, stack, env); assert(jl_is_unionall(dt)); jl_unionall_t *ua = (jl_unionall_t*)dt; jl_typeenv_t e = { ua->var, iparams[c], env }; - return inst_datatype_env(ua->body, p, iparams, ntp, cacheable, stack, &e, c + 1); + return inst_datatype_env(ua->body, p, iparams, ntp, stack, &e, c + 1); } jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n) @@ -923,14 +925,7 @@ jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n) jl_value_t *u = jl_unwrap_unionall(tc); if (jl_is_datatype(u) && n == jl_nparams((jl_datatype_t*)u) && ((jl_datatype_t*)u)->name->wrapper == tc) { - int cacheable = 1; - for (i = 0; i < n; i++) { - if (jl_has_free_typevars(params[i])) { - cacheable = 0; - break; - } - } - return inst_datatype_env(tc, NULL, params, n, cacheable, NULL, NULL, 0); + return inst_datatype_env(tc, NULL, params, n, NULL, NULL, 0); } } JL_GC_PUSH1(&tc); @@ -977,20 +972,42 @@ jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n) JL_DLLEXPORT jl_value_t *jl_apply_type1(jl_value_t *tc, jl_value_t *p1) { - JL_GC_PUSH1(&p1); - jl_value_t *t = jl_apply_type(tc, &p1, 1); - JL_GC_POP(); - return t; + return jl_apply_type(tc, &p1, 1); } JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2) { - jl_value_t **args; - JL_GC_PUSHARGS(args, 2); - args[0] = p1; args[1] = p2; - jl_value_t *t = jl_apply_type(tc, args, 2); - JL_GC_POP(); - return t; + jl_value_t *args[2]; + args[0] = p1; + args[1] = p2; + return jl_apply_type(tc, args, 2); +} + +jl_datatype_t *jl_apply_modify_type(jl_value_t *dt) +{ + jl_datatype_t *rettyp = (jl_datatype_t*)jl_apply_type2(jl_pair_type, dt, dt); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + return rettyp; +} + +jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt) +{ + jl_value_t *params[2]; + jl_value_t *names = jl_atomic_load_relaxed(&cmpswap_names); + if (names == NULL) { + params[0] = (jl_value_t*)jl_symbol("old"); + params[1] = (jl_value_t*)jl_symbol("success"); + jl_value_t *lnames = jl_f_tuple(NULL, params, 2); + if (jl_atomic_cmpswap(&cmpswap_names, &names, lnames)) + names = jl_atomic_load_relaxed(&cmpswap_names); // == lnames + } + params[0] = dt; + params[1] = (jl_value_t*)jl_bool_type; + jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) + jl_datatype_t *rettyp = (jl_datatype_t*)jl_apply_type2((jl_value_t*)jl_namedtuple_type, names, (jl_value_t*)tuptyp); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + return rettyp; } JL_DLLEXPORT jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v) @@ -1309,8 +1326,15 @@ jl_value_t *normalize_unionalls(jl_value_t *t) u = (jl_unionall_t*)t; } - if (u->var->lb == u->var->ub || may_substitute_ub(body, u->var)) - t = jl_instantiate_unionall(u, u->var->ub); + if (u->var->lb == u->var->ub || may_substitute_ub(body, u->var)) { + JL_TRY { + t = jl_instantiate_unionall(u, u->var->ub); + } + JL_CATCH { + // just skip normalization + // (may happen for bounds inconsistent with the wrapper's bounds) + } + } } JL_GC_POP(); return t; @@ -1319,18 +1343,32 @@ jl_value_t *normalize_unionalls(jl_value_t *t) static jl_value_t *_jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_t *env, jl_value_t **vals, jl_typeenv_t *prev, jl_typestack_t *stack); static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value_t **iparams, size_t ntp, - int cacheable, jl_typestack_t *stack, jl_typeenv_t *env) + jl_typestack_t *stack, jl_typeenv_t *env) { jl_typestack_t top; jl_typename_t *tn = dt->name; int istuple = (tn == jl_tuple_typename); int isnamedtuple = (tn == jl_namedtuple_typename); if (dt->name != jl_type_typename) { - for (size_t i = 0; i < ntp; i++) + size_t i; + for (i = 0; i < ntp; i++) iparams[i] = normalize_unionalls(iparams[i]); } - // check type cache + // check type cache, if applicable + int cacheable = 1; + if (istuple) { + size_t i; + for (i = 0; cacheable && i < ntp; i++) + if (!jl_is_concrete_type(iparams[i]) && iparams[i] != jl_bottom_type) + cacheable = 0; + } + else { + size_t i; + for (i = 0; cacheable && i < ntp; i++) + if (jl_has_free_typevars(iparams[i])) + cacheable = 0; + } if (cacheable) { size_t i; for (i = 0; i < ntp; i++) { @@ -1516,9 +1554,8 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value // leading to incorrect layouts and data races (#40050: the A{T} should be // an isbitstype singleton of size 0) if (cacheable) { - if (dt->layout == NULL && !jl_is_primitivetype(dt) && ndt->types != NULL && ndt->isconcretetype) { + if (ndt->layout == NULL && ndt->types != NULL && ndt->isconcretetype) jl_compute_field_offsets(ndt); - } jl_cache_type_(ndt); JL_UNLOCK(&typecache_lock); // Might GC } @@ -1529,13 +1566,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value static jl_tupletype_t *jl_apply_tuple_type_v_(jl_value_t **p, size_t np, jl_svec_t *params) { - int cacheable = 1; - for (size_t i = 0; i < np; i++) { - assert(p[i]); - if (!jl_is_concrete_type(p[i]) && p[i] != jl_bottom_type) - cacheable = 0; - } - return (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, p, np, cacheable, NULL, NULL); + return (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, p, np, NULL, NULL); } JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type(jl_svec_t *params) @@ -1557,7 +1588,6 @@ jl_tupletype_t *jl_inst_arg_tuple_type(jl_value_t *arg1, jl_value_t **args, size { jl_tupletype_t *tt = (jl_datatype_t*)lookup_typevalue(jl_tuple_typename, arg1, args, nargs, leaf); if (tt == NULL) { - int cacheable = 1; size_t i; jl_svec_t *params = jl_alloc_svec(nargs); JL_GC_PUSH1(¶ms); @@ -1569,14 +1599,13 @@ jl_tupletype_t *jl_inst_arg_tuple_type(jl_value_t *arg1, jl_value_t **args, size // `jl_typeof(ai)`, but that will require some redesign of the caching // logic. ai = (jl_value_t*)jl_wrap_Type(ai); - cacheable = 0; } else { ai = jl_typeof(ai); } jl_svecset(params, i, ai); } - tt = (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, jl_svec_data(params), nargs, cacheable, NULL, NULL); + tt = (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, jl_svec_data(params), nargs, NULL, NULL); JL_GC_POP(); } return tt; @@ -1644,9 +1673,6 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_ iparams = jl_svec_data(ip_heap); } int bound = 0; - int cacheable = 1; - if (jl_is_va_tuple(tt)) - cacheable = 0; int i; for (i = 0; i < ntp; i++) { jl_value_t *elt = jl_svecref(tp, i); @@ -1655,11 +1681,9 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_ if (ip_heap) jl_gc_wb(ip_heap, pi); bound |= (pi != elt); - if (cacheable && !jl_is_concrete_type(pi)) - cacheable = 0; } if (bound) - t = inst_datatype_inner(tt, ip_heap, iparams, ntp, cacheable, stack, env); + t = inst_datatype_inner(tt, ip_heap, iparams, ntp, stack, env); JL_GC_POP(); return t; } @@ -1746,18 +1770,16 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t size_t ntp = jl_svec_len(tp); jl_value_t **iparams; JL_GC_PUSHARGS(iparams, ntp); - int cacheable = 1, bound = 0; + int bound = 0; for (i = 0; i < ntp; i++) { jl_value_t *elt = jl_svecref(tp, i); jl_value_t *pi = inst_type_w_(elt, env, stack, check); iparams[i] = pi; bound |= (pi != elt); - if (cacheable && jl_has_free_typevars(pi)) - cacheable = 0; } // if t's parameters are not bound in the environment, return it uncopied (#9378) if (bound) - t = inst_datatype_inner(tt, NULL, iparams, ntp, cacheable, stack, env); + t = inst_datatype_inner(tt, NULL, iparams, ntp, stack, env); JL_GC_POP(); return t; } diff --git a/src/julia.h b/src/julia.h index 3455817cf1a92..b2a8bd15bcb22 100644 --- a/src/julia.h +++ b/src/julia.h @@ -726,6 +726,7 @@ extern JL_DLLIMPORT jl_typename_t *jl_llvmpointer_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_typename_t *jl_namedtuple_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_namedtuple_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_task_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_pair_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint8_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_any_type JL_GLOBALLY_ROOTED; @@ -1381,6 +1382,8 @@ JL_DLLEXPORT jl_value_t *jl_instantiate_unionall(jl_unionall_t *u, jl_value_t *p JL_DLLEXPORT jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n); JL_DLLEXPORT jl_value_t *jl_apply_type1(jl_value_t *tc, jl_value_t *p1); JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2); +JL_DLLEXPORT jl_datatype_t *jl_apply_modify_type(jl_value_t *dt); +JL_DLLEXPORT jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt); JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type(jl_svec_t *params); JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np); JL_DLLEXPORT jl_datatype_t *jl_new_datatype(jl_sym_t *name, @@ -1403,7 +1406,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_new_bits(jl_value_t *dt, const char *src); JL_DLLEXPORT void jl_atomic_store_bits(char *dst, const jl_value_t *src, int nb); JL_DLLEXPORT jl_value_t *jl_atomic_swap_bits(jl_value_t *dt, char *dst, const jl_value_t *src, int nb); JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expected, const jl_value_t *src, int nb); -JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb); +JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t *rettype, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb); JL_DLLEXPORT jl_value_t *jl_new_struct(jl_datatype_t *type, ...); JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, uint32_t na); JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup); diff --git a/src/julia_internal.h b/src/julia_internal.h index f4d63cc27cf78..673d4459bef03 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -154,8 +154,9 @@ static inline uint64_t cycleclock(void) #include "timing.h" -extern uint8_t *jl_measure_compile_time; -extern uint64_t *jl_cumulative_compile_time; +// Global *atomic* integers controlling *process-wide* measurement of compilation time. +extern uint8_t jl_measure_compile_time_enabled; +extern uint64_t jl_cumulative_compile_time; #ifdef _COMPILER_MICROSOFT_ # define jl_return_address() ((uintptr_t)_ReturnAddress()) @@ -1325,6 +1326,7 @@ void jl_log(int level, jl_value_t *module, jl_value_t *group, jl_value_t *id, int isabspath(const char *in) JL_NOTSAFEPOINT; extern jl_sym_t *call_sym; extern jl_sym_t *invoke_sym; +extern jl_sym_t *invoke_modify_sym; extern jl_sym_t *empty_sym; extern jl_sym_t *top_sym; extern jl_sym_t *module_sym; extern jl_sym_t *slot_sym; extern jl_sym_t *export_sym; extern jl_sym_t *import_sym; diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index ec7060bd10a5e..f7130f6904479 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -628,6 +628,21 @@ void Optimizer::checkInst(Instruction *I) use_info.hasunknownmem = true; return true; } + if (isa(inst) || isa(inst)) { + // Only store value count + if (use->getOperandNo() != isa(inst) ? AtomicCmpXchgInst::getPointerOperandIndex() : AtomicRMWInst::getPointerOperandIndex()) { + use_info.escaped = true; + return false; + } + use_info.hasload = true; + auto storev = isa(inst) ? cast(inst)->getNewValOperand() : cast(inst)->getValOperand(); + if (cur.offset == UINT32_MAX || !use_info.addMemOp(inst, use->getOperandNo(), + cur.offset, storev->getType(), + true, *pass.DL)) + use_info.hasunknownmem = true; + use_info.refload = true; + return true; + } if (isa(inst) || isa(inst)) { push_inst(inst); return true; @@ -1149,6 +1164,7 @@ void Optimizer::optimizeTag(CallInst *orig_inst) { auto tag = orig_inst->getArgOperand(2); // `julia.typeof` is only legal on the original pointer, no need to scan recursively + size_t last_deleted = removed.size(); for (auto user: orig_inst->users()) { if (auto call = dyn_cast(user)) { auto callee = call->getCalledOperand(); @@ -1161,6 +1177,8 @@ void Optimizer::optimizeTag(CallInst *orig_inst) } } } + while (last_deleted < removed.size()) + removed[last_deleted++]->replaceUsesOfWith(orig_inst, UndefValue::get(orig_inst->getType())); } void Optimizer::splitOnStack(CallInst *orig_inst) @@ -1328,6 +1346,22 @@ void Optimizer::splitOnStack(CallInst *orig_inst) store->eraseFromParent(); return; } + else if (isa(user) || isa(user)) { + auto slot_idx = find_slot(offset); + auto &slot = slots[slot_idx]; + assert(slot.offset <= offset && slot.offset + slot.size >= offset); + IRBuilder<> builder(user); + Value *newptr; + if (slot.isref) { + assert(slot.offset == offset); + newptr = slot.slot; + } + else { + Value *Val = isa(user) ? cast(user)->getNewValOperand() : cast(user)->getValOperand(); + newptr = slot_gep(slot, offset, Val->getType(), builder); + } + *use = newptr; + } else if (auto call = dyn_cast(user)) { auto callee = call->getCalledOperand(); assert(callee); // makes it clear for clang analyser that `callee` is not NULL diff --git a/src/method.c b/src/method.c index 48b074e800904..22145a4349853 100644 --- a/src/method.c +++ b/src/method.c @@ -831,7 +831,7 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, // TODO: derive our debug name from the syntax instead of the type name = mt->name; - if (mt == jl_type_type_mt || mt == jl_nonfunction_mt) { + if (mt == jl_type_type_mt || mt == jl_nonfunction_mt || external_mt) { // our value for `name` is bad, try to guess what the syntax might have had, // like `jl_static_show_func_sig` might have come up with jl_datatype_t *dt = jl_first_argument_datatype(argtype); diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 7cb58bc230294..741bb5448b847 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -142,15 +142,25 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerswap(jl_value_t *p, jl_value_t *x, jl_ return y; } -JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, jl_value_t *x, jl_value_t *order_sym) +JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, jl_value_t *x, jl_value_t *order) { - // n.b. we use seq_cst always here, but need to verify the order sym - // against the weaker load-only that happens first - if (order_sym == (jl_value_t*)acquire_release_sym) - order_sym = (jl_value_t*)acquire_sym; - jl_value_t *expected = jl_atomic_pointerref(p, order_sym); + JL_TYPECHK(atomic_pointerref, pointer, p); + JL_TYPECHK(atomic_pointerref, symbol, order) + (void)jl_get_atomic_order_checked((jl_sym_t*)order, 1, 1); jl_value_t *ety = jl_tparam0(jl_typeof(p)); char *pp = (char*)jl_unbox_long(p); + jl_value_t *expected; + if (ety == (jl_value_t*)jl_any_type) { + expected = jl_atomic_load((jl_value_t**)pp); + } + else { + if (!is_valid_intrinsic_elptr(ety)) + jl_error("atomic_pointermodify: invalid pointer"); + size_t nb = jl_datatype_size(ety); + if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) + jl_error("atomic_pointermodify: invalid pointer for atomic operation"); + expected = jl_atomic_new_bits(ety, pp); + } jl_value_t **args; JL_GC_PUSHARGS(args, 2); args[0] = expected; @@ -175,12 +185,16 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, j args[0] = expected; jl_gc_safepoint(); } - // args[0] == expected (old); args[1] == y (new) - args[0] = jl_f_tuple(NULL, args, 2); + // args[0] == expected (old) + // args[1] == y (new) + jl_datatype_t *rettyp = jl_apply_modify_type(ety); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + args[0] = jl_new_struct(rettyp, args[0], args[1]); JL_GC_POP(); return args[0]; } + JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *expected, jl_value_t *x, jl_value_t *success_order_sym, jl_value_t *failure_order_sym) { JL_TYPECHK(atomic_pointerreplace, pointer, p); @@ -193,20 +207,21 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp // TODO: filter other invalid orderings jl_value_t *ety = jl_tparam0(jl_typeof(p)); char *pp = (char*)jl_unbox_long(p); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(ety); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) if (ety == (jl_value_t*)jl_any_type) { - jl_value_t **result; - JL_GC_PUSHARGS(result, 2); - result[0] = expected; + jl_value_t *result; + JL_GC_PUSH1(&result); + result = expected; int success; while (1) { - success = jl_atomic_cmpswap((jl_value_t**)pp, &result[0], x); - if (success || !jl_egal(result[0], expected)) + success = jl_atomic_cmpswap((jl_value_t**)pp, &result, x); + if (success || !jl_egal(result, expected)) break; } - result[1] = success ? jl_true : jl_false; - result[0] = jl_f_tuple(NULL, result, 2); + result = jl_new_struct(rettyp, result, success ? jl_true : jl_false); JL_GC_POP(); - return result[0]; + return result; } else { if (!is_valid_intrinsic_elptr(ety)) @@ -216,7 +231,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp size_t nb = jl_datatype_size(ety); if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) jl_error("atomic_pointerreplace: invalid pointer for atomic operation"); - return jl_atomic_cmpswap_bits((jl_datatype_t*)ety, pp, expected, x, nb); + return jl_atomic_cmpswap_bits((jl_datatype_t*)ety, rettyp, pp, expected, x, nb); } } diff --git a/src/signals-win.c b/src/signals-win.c index 984330dc434dc..5d1a963078df4 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -141,7 +141,7 @@ void jl_throw_in_ctx(jl_value_t *excpt, PCONTEXT ctxThread) ct->gcstack); } else if (have_backtrace_fiber) { - JL_LOCK(&backtrace_lock); + JL_LOCK_NOGC(&backtrace_lock); stkerror_ctx = ctxThread; stkerror_ptls = ptls; jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber); diff --git a/src/staticdata.c b/src/staticdata.c index 8fa1613b075a8..f5892d4218e71 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -30,7 +30,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 150 +#define NUM_TAGS 151 // 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. @@ -127,6 +127,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_floatingpoint_type); INSERT_TAG(jl_number_type); INSERT_TAG(jl_signed_type); + INSERT_TAG(jl_pair_type); // special typenames INSERT_TAG(jl_tuple_typename); diff --git a/src/subtype.c b/src/subtype.c index 158a9dd70b3f3..c3512eeb17dac 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -42,11 +42,19 @@ extern "C" { // TODO: the stack probably needs to be artificially large because of some // deeper problem (see #21191) and could be shrunk once that is fixed typedef struct { - int depth; - int more; + int16_t depth; + int16_t more; + int16_t used; uint32_t stack[100]; // stack of bits represented as a bit vector } jl_unionstate_t; +typedef struct { + int16_t depth; + int16_t more; + int16_t used; + void *stack; +} jl_saved_unionstate_t; + // Linked list storing the type variable environment. A new jl_varbinding_t // is pushed for each UnionAll type we encounter. `lb` and `ub` are updated // during the computation. @@ -60,22 +68,21 @@ typedef struct jl_varbinding_t { int8_t occurs_inv; // occurs in invariant position int8_t occurs_cov; // # of occurrences in covariant position int8_t concrete; // 1 if another variable has a constraint forcing this one to be concrete - // in covariant position, we need to try constraining a variable in different ways: - // 0 - unconstrained - // 1 - less than - // 2 - greater than - // 3 - inexpressible - occurs when the var has non-trivial overlap with another type, - // and we would need to return `intersect(var,other)`. in this case - // we choose to over-estimate the intersection by returning the var. + // constraintkind: in covariant position, we try three different ways to compute var ∩ type: + // let ub = var.ub ∩ type + // 0 - var.ub <: type ? var : ub + // 1 - var.ub = ub; return var + // 2 - either (var.ub = ub; return var), or return ub int8_t constraintkind; - int depth0; // # of invariant constructors nested around the UnionAll type for this var + int8_t intvalued; // must be integer-valued; i.e. occurs as N in Vararg{_,N} + int8_t limited; + int16_t depth0; // # of invariant constructors nested around the UnionAll type for this var // when this variable's integer value is compared to that of another, // it equals `other + offset`. used by vararg length parameters. - int offset; + int16_t offset; // array of typevars that our bounds depend on, whose UnionAlls need to be // moved outside ours. jl_array_t *innervars; - int intvalued; // must be integer-valued; i.e. occurs as N in Vararg{_,N} struct jl_varbinding_t *prev; } jl_varbinding_t; @@ -94,6 +101,7 @@ typedef struct jl_stenv_t { int ignore_free; // treat free vars as black boxes; used during intersection int intersection; // true iff subtype is being called from intersection int emptiness_only; // true iff intersection only needs to test for emptiness + int triangular; // when intersecting Ref{X} with Ref{<:Y} } jl_stenv_t; // state manipulation utilities @@ -129,6 +137,23 @@ static void statestack_set(jl_unionstate_t *st, int i, int val) JL_NOTSAFEPOINT st->stack[i>>5] &= ~(1u<<(i&31)); } +#define push_unionstate(saved, src) \ + do { \ + (saved)->depth = (src)->depth; \ + (saved)->more = (src)->more; \ + (saved)->used = (src)->used; \ + (saved)->stack = alloca(((src)->used+7)/8); \ + memcpy((saved)->stack, &(src)->stack, ((src)->used+7)/8); \ + } while (0); + +#define pop_unionstate(dst, saved) \ + do { \ + (dst)->depth = (saved)->depth; \ + (dst)->more = (saved)->more; \ + (dst)->used = (saved)->used; \ + memcpy(&(dst)->stack, (saved)->stack, ((saved)->used+7)/8); \ + } while (0); + typedef struct { int8_t *buf; int rdepth; @@ -486,6 +511,10 @@ static jl_value_t *pick_union_element(jl_value_t *u JL_PROPAGATES_ROOT, jl_stenv { jl_unionstate_t *state = R ? &e->Runions : &e->Lunions; do { + if (state->depth >= state->used) { + statestack_set(state, state->used, 0); + state->used++; + } int ui = statestack_get(state, state->depth); state->depth++; if (ui == 0) { @@ -514,11 +543,10 @@ static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) return 1; if (x == (jl_value_t*)jl_any_type && jl_is_datatype(y)) return 0; - jl_unionstate_t oldLunions = e->Lunions; - jl_unionstate_t oldRunions = e->Runions; + jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); + jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); int sub; - memset(e->Lunions.stack, 0, sizeof(e->Lunions.stack)); - memset(e->Runions.stack, 0, sizeof(e->Runions.stack)); + e->Lunions.used = e->Runions.used = 0; e->Runions.depth = 0; e->Runions.more = 0; e->Lunions.depth = 0; @@ -526,8 +554,8 @@ static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) sub = forall_exists_subtype(x, y, e, 0); - e->Runions = oldRunions; - e->Lunions = oldLunions; + pop_unionstate(&e->Runions, &oldRunions); + pop_unionstate(&e->Lunions, &oldLunions); return sub; } @@ -731,8 +759,8 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e) static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param) { u = unalias_unionall(u, e); - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, - R ? e->Rinvdepth : e->invdepth, 0, NULL, 0, e->vars }; + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, + R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; JL_GC_PUSH4(&u, &vb.lb, &vb.ub, &vb.innervars); e->vars = &vb; int ans; @@ -1148,6 +1176,10 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // union against the variable before trying to take it apart to see if there are any // variables lurking inside. jl_unionstate_t *state = &e->Runions; + if (state->depth >= state->used) { + statestack_set(state, state->used, 0); + state->used++; + } ui = statestack_get(state, state->depth); state->depth++; if (ui == 0) @@ -1310,13 +1342,13 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) (is_definite_length_tuple_type(x) && is_indefinite_length_tuple_type(y))) return 0; - jl_unionstate_t oldLunions = e->Lunions; - memset(e->Lunions.stack, 0, sizeof(e->Lunions.stack)); + jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); + e->Lunions.used = 0; int sub; if (!jl_has_free_typevars(x) || !jl_has_free_typevars(y)) { - jl_unionstate_t oldRunions = e->Runions; - memset(e->Runions.stack, 0, sizeof(e->Runions.stack)); + jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); + e->Runions.used = 0; e->Runions.depth = 0; e->Runions.more = 0; e->Lunions.depth = 0; @@ -1324,7 +1356,7 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) sub = forall_exists_subtype(x, y, e, 2); - e->Runions = oldRunions; + pop_unionstate(&e->Runions, &oldRunions); } else { int lastset = 0; @@ -1342,13 +1374,13 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) } } - e->Lunions = oldLunions; + pop_unionstate(&e->Lunions, &oldLunions); return sub && subtype(y, x, e, 0); } static int exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, jl_value_t *saved, jl_savedenv_t *se, int param) { - memset(e->Runions.stack, 0, sizeof(e->Runions.stack)); + e->Runions.used = 0; int lastset = 0; while (1) { e->Runions.depth = 0; @@ -1379,7 +1411,7 @@ static int forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, in JL_GC_PUSH1(&saved); save_env(e, &saved, &se); - memset(e->Lunions.stack, 0, sizeof(e->Lunions.stack)); + e->Lunions.used = 0; int lastset = 0; int sub; while (1) { @@ -1413,8 +1445,10 @@ static void init_stenv(jl_stenv_t *e, jl_value_t **env, int envsz) e->ignore_free = 0; e->intersection = 0; e->emptiness_only = 0; + e->triangular = 0; e->Lunions.depth = 0; e->Runions.depth = 0; e->Lunions.more = 0; e->Runions.more = 0; + e->Lunions.used = 0; e->Runions.used = 0; } // subtyping entry points @@ -2084,14 +2118,14 @@ static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, if (y == (jl_value_t*)jl_any_type && !jl_is_typevar(x)) return x; - jl_unionstate_t oldRunions = e->Runions; + jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); int savedepth = e->invdepth, Rsavedepth = e->Rinvdepth; // TODO: this doesn't quite make sense e->invdepth = e->Rinvdepth = d; jl_value_t *res = intersect_all(x, y, e); - e->Runions = oldRunions; + pop_unionstate(&e->Runions, &oldRunions); e->invdepth = savedepth; e->Rinvdepth = Rsavedepth; return res; @@ -2102,10 +2136,10 @@ static jl_value_t *intersect_union(jl_value_t *x, jl_uniontype_t *u, jl_stenv_t if (param == 2 || (!jl_has_free_typevars(x) && !jl_has_free_typevars((jl_value_t*)u))) { jl_value_t *a=NULL, *b=NULL; JL_GC_PUSH2(&a, &b); - jl_unionstate_t oldRunions = e->Runions; + jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); a = R ? intersect_all(x, u->a, e) : intersect_all(u->a, x, e); b = R ? intersect_all(x, u->b, e) : intersect_all(u->b, x, e); - e->Runions = oldRunions; + pop_unionstate(&e->Runions, &oldRunions); jl_value_t *i = simple_join(a,b); JL_GC_POP(); return i; @@ -2171,7 +2205,7 @@ static void set_bound(jl_value_t **bound, jl_value_t *val, jl_tvar_t *v, jl_sten return; jl_varbinding_t *btemp = e->vars; while (btemp != NULL) { - if (btemp->lb == (jl_value_t*)v && btemp->ub == (jl_value_t*)v && + if ((btemp->lb == (jl_value_t*)v || btemp->ub == (jl_value_t*)v) && in_union(val, (jl_value_t*)btemp->var)) return; btemp = btemp->prev; @@ -2223,6 +2257,21 @@ static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) return reachable_var(xv->ub, y, e) || reachable_var(xv->lb, y, e); } +// check whether setting v == t implies v == SomeType{v}, which is unsatisfiable. +static int check_unsat_bound(jl_value_t *t, jl_tvar_t *v, jl_stenv_t *e) JL_NOTSAFEPOINT +{ + if (var_occurs_inside(t, v, 0, 0)) + return 1; + jl_varbinding_t *btemp = e->vars; + while (btemp != NULL) { + if (btemp->lb == (jl_value_t*)v && btemp->ub == (jl_value_t*)v && + var_occurs_inside(t, btemp->var, 0, 0)) + return 1; + btemp = btemp->prev; + } + return 0; +} + static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int8_t R, int param) { jl_varbinding_t *bb = lookup(e, b); @@ -2252,7 +2301,9 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int ub = a; } else { + e->triangular++; ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); + e->triangular--; save_env(e, &root, &se); int issub = subtype_in_env_existential(bb->lb, ub, e, 0, d); restore_env(e, root, &se); @@ -2264,20 +2315,10 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int } if (ub != (jl_value_t*)b) { if (jl_has_free_typevars(ub)) { - // constraint X == Ref{X} is unsatisfiable. also check variables set equal to X. - if (var_occurs_inside(ub, b, 0, 0)) { + if (check_unsat_bound(ub, b, e)) { JL_GC_POP(); return jl_bottom_type; } - jl_varbinding_t *btemp = e->vars; - while (btemp != NULL) { - if (btemp->lb == (jl_value_t*)b && btemp->ub == (jl_value_t*)b && - var_occurs_inside(ub, btemp->var, 0, 0)) { - JL_GC_POP(); - return jl_bottom_type; - } - btemp = btemp->prev; - } } bb->ub = ub; bb->lb = ub; @@ -2285,67 +2326,33 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int JL_GC_POP(); return ub; } - else if (bb->constraintkind == 0) { - if (!jl_is_typevar(bb->ub) && !jl_is_typevar(a)) { - if (try_subtype_in_env(bb->ub, a, e, 0, d)) - return (jl_value_t*)b; - } - return R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); - } - else if (bb->concrete || bb->constraintkind == 1) { - jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); - if (ub == jl_bottom_type) - return jl_bottom_type; - JL_GC_PUSH1(&ub); - if (!R && !subtype_bounds_in_env(bb->lb, a, e, 0, d)) { - // this fixes issue #30122. TODO: better fix for R flag. - JL_GC_POP(); - return jl_bottom_type; - } - JL_GC_POP(); - set_bound(&bb->ub, ub, b, e); - return (jl_value_t*)b; - } - else if (bb->constraintkind == 2) { - // TODO: removing this case fixes many test_brokens in test/subtype.jl - // but breaks other tests. - if (!subtype_bounds_in_env(a, bb->ub, e, 1, d)) { - // mark var as unsatisfiable by making it circular - bb->lb = (jl_value_t*)b; - return jl_bottom_type; - } - jl_value_t *lb = simple_join(bb->lb, a); - set_bound(&bb->lb, lb, b, e); - return a; - } - assert(bb->constraintkind == 3); jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); if (ub == jl_bottom_type) return jl_bottom_type; - if (jl_is_typevar(a)) + if (bb->constraintkind == 1 || e->triangular) { + if (e->triangular && check_unsat_bound(ub, b, e)) + return jl_bottom_type; + set_bound(&bb->ub, ub, b, e); return (jl_value_t*)b; - if (ub == a) { - if (bb->lb == jl_bottom_type) { - set_bound(&bb->ub, a, b, e); + } + else if (bb->constraintkind == 0) { + JL_GC_PUSH1(&ub); + if (!jl_is_typevar(a) && try_subtype_in_env(bb->ub, a, e, 0, d)) { + JL_GC_POP(); return (jl_value_t*)b; } + JL_GC_POP(); return ub; } - else if (bb->ub == bb->lb) { - return ub; - } - root = NULL; - JL_GC_PUSH2(&root, &ub); - save_env(e, &root, &se); - jl_value_t *ii = R ? intersect_aside(a, bb->lb, e, 1, d) : intersect_aside(bb->lb, a, e, 0, d); - if (ii == jl_bottom_type) { - restore_env(e, root, &se); - ii = (jl_value_t*)b; + assert(bb->constraintkind == 2); + if (!jl_is_typevar(a)) { + if (ub == a && bb->lb != jl_bottom_type) + return ub; + else if (jl_egal(bb->ub, bb->lb)) + return ub; set_bound(&bb->ub, ub, b, e); } - free_env(&se); - JL_GC_POP(); - return ii; + return (jl_value_t*)b; } // test whether `var` occurs inside constructors. `want_inv` tests only inside @@ -2389,7 +2396,7 @@ static int var_occurs_inside(jl_value_t *v, jl_tvar_t *var, int inside, int want } // Caller might not have rooted `res` -static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbinding_t *vb, jl_stenv_t *e) +static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbinding_t *vb, jl_unionall_t *u, jl_stenv_t *e) { jl_value_t *varval = NULL; jl_tvar_t *newvar = vb->var; @@ -2402,7 +2409,10 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind // given x<:T<:x, substitute x for T varval = vb->ub; } - else if (!vb->occurs_inv && is_leaf_bound(vb->ub)) { + // TODO: `vb.occurs_cov == 1` here allows substituting Tuple{<:X} => Tuple{X}, + // which is valid but changes some ambiguity errors so we don't need to do it yet. + else if ((/*vb->occurs_cov == 1 || */is_leaf_bound(vb->ub)) && + !var_occurs_invariant(u->body, u->var, 0)) { // replace T<:x with x in covariant position when possible varval = vb->ub; } @@ -2420,9 +2430,8 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind } } - // prefer generating a fresh typevar, to avoid repeated renaming if the result - // is compared to one of the intersected types later. - if (!varval) + // TODO: this can prevent us from matching typevar identities later + if (!varval && (vb->lb != vb->var->lb || vb->ub != vb->var->ub)) newvar = jl_new_typevar(vb->var->name, vb->lb, vb->ub); // remove/replace/rewrap free occurrences of this var in the environment @@ -2540,8 +2549,10 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv int envsize = 0; while (btemp != NULL) { envsize++; - if (envsize > 150) + if (envsize > 120) { + vb->limited = 1; return t; + } if (btemp->var == u->var || btemp->lb == (jl_value_t*)u->var || btemp->ub == (jl_value_t*)u->var) { u = rename_unionall(u); @@ -2591,46 +2602,37 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv } if (res != jl_bottom_type) // res is rooted by callee - res = finish_unionall(res, vb, e); + res = finish_unionall(res, vb, u, e); JL_GC_POP(); return res; } static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param) { - jl_value_t *res=NULL, *res2=NULL, *save=NULL, *save2=NULL; - jl_savedenv_t se, se2; - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, - R ? e->Rinvdepth : e->invdepth, 0, NULL, 0, e->vars }; - JL_GC_PUSH6(&res, &save2, &vb.lb, &vb.ub, &save, &vb.innervars); + jl_value_t *res=NULL, *save=NULL; + jl_savedenv_t se; + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, + R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; + JL_GC_PUSH5(&res, &vb.lb, &vb.ub, &save, &vb.innervars); save_env(e, &save, &se); res = intersect_unionall_(t, u, e, R, param, &vb); - if (res != jl_bottom_type) { + if (vb.limited) { + // if the environment got too big, avoid tree recursion and propagate the flag + if (e->vars) + e->vars->limited = 1; + } + else if (res != jl_bottom_type) { if (vb.concrete || vb.occurs_inv>1 || u->var->lb != jl_bottom_type || (vb.occurs_inv && vb.occurs_cov)) { restore_env(e, NULL, &se); vb.occurs_cov = vb.occurs_inv = 0; - vb.constraintkind = 3; + vb.constraintkind = vb.concrete ? 1 : 2; res = intersect_unionall_(t, u, e, R, param, &vb); } - else if (vb.occurs_cov) { - save_env(e, &save2, &se2); + else if (vb.occurs_cov && !var_occurs_invariant(u->body, u->var, 0)) { restore_env(e, save, &se); vb.occurs_cov = vb.occurs_inv = 0; - vb.lb = u->var->lb; vb.ub = u->var->ub; vb.constraintkind = 1; - res2 = intersect_unionall_(t, u, e, R, param, &vb); - if (res2 == jl_bottom_type) { - restore_env(e, save, &se); - vb.occurs_cov = vb.occurs_inv = 0; - vb.lb = u->var->lb; vb.ub = u->var->ub; - vb.constraintkind = 2; - res2 = intersect_unionall_(t, u, e, R, param, &vb); - if (res2 == jl_bottom_type) - restore_env(e, save2, &se2); - } - if (res2 != jl_bottom_type) - res = res2; - free_env(&se2); + res = intersect_unionall_(t, u, e, R, param, &vb); } } free_env(&se); @@ -3016,14 +3018,13 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa jl_value_t *ub=NULL, *lb=NULL; JL_GC_PUSH2(&lb, &ub); ub = intersect_aside(xub, yub, e, 0, xx ? xx->depth0 : 0); - if (xlb == y) + if (reachable_var(xlb, (jl_tvar_t*)y, e)) lb = ylb; else lb = simple_join(xlb, ylb); if (yy) { - if (!subtype_by_bounds(lb, y, e)) - yy->lb = lb; - if (!subtype_by_bounds(y, ub, e)) + yy->lb = lb; + if (!reachable_var(ub, (jl_tvar_t*)y, e)) yy->ub = ub; assert(yy->ub != y); assert(yy->lb != y); @@ -3159,7 +3160,7 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { e->Runions.depth = 0; e->Runions.more = 0; - memset(e->Runions.stack, 0, sizeof(e->Runions.stack)); + e->Runions.used = 0; jl_value_t **is; JL_GC_PUSHARGS(is, 3); jl_value_t **saved = &is[2]; @@ -3176,11 +3177,8 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) save_env(e, saved, &se); } while (e->Runions.more) { - if (e->emptiness_only && ii != jl_bottom_type) { - free_env(&se); - JL_GC_POP(); - return ii; - } + if (e->emptiness_only && ii != jl_bottom_type) + break; e->Runions.depth = 0; int set = e->Runions.more - 1; e->Runions.more = 0; @@ -3209,9 +3207,8 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) } total_iter++; if (niter > 3 || total_iter > 400000) { - free_env(&se); - JL_GC_POP(); - return y; + ii = y; + break; } } free_env(&se); diff --git a/src/support/htable.inc b/src/support/htable.inc index fa59624a4998f..7a9be2514e2f0 100644 --- a/src/support/htable.inc +++ b/src/support/htable.inc @@ -13,67 +13,77 @@ static void **HTNAME##_lookup_bp_r(htable_t *h, void *key, void *ctx) \ { \ uint_t hv; \ - size_t i, orig, index, iter; \ + size_t i, orig, index, iter, empty_slot; \ size_t newsz, sz = hash_size(h); \ size_t maxprobe = max_probe(sz); \ void **tab = h->table; \ void **ol; \ \ hv = HFUNC((uintptr_t)key, ctx); \ - retry_bp: \ - iter = 0; \ - index = (size_t)(hv & (sz-1)) * 2; \ - sz *= 2; \ - orig = index; \ - \ - do { \ - if (tab[index+1] == HT_NOTFOUND) { \ - tab[index] = key; \ - return &tab[index+1]; \ + while (1) { \ + iter = 0; \ + index = (size_t)(hv & (sz-1)) * 2; \ + sz *= 2; \ + orig = index; \ + empty_slot = -1; \ + \ + do { \ + if (tab[index] == HT_NOTFOUND) { \ + if (empty_slot == -1) \ + empty_slot = index; \ + break; \ + } \ + if (tab[index+1] == HT_NOTFOUND) { \ + if (empty_slot == -1) \ + empty_slot = index; \ + } \ + \ + if (EQFUNC(key, tab[index], ctx)) \ + return &tab[index+1]; \ + \ + index = (index+2) & (sz-1); \ + iter++; \ + if (iter > maxprobe) \ + break; \ + } while (index != orig); \ + \ + if (empty_slot != -1) { \ + tab[empty_slot] = key; \ + return &tab[empty_slot+1]; \ } \ \ - if (EQFUNC(key, tab[index], ctx)) \ - return &tab[index+1]; \ - \ - index = (index+2) & (sz-1); \ - iter++; \ - if (iter > maxprobe) \ - break; \ - } while (index != orig); \ - \ - /* table full */ \ - /* quadruple size, rehash, retry the insert */ \ - /* it's important to grow the table really fast; otherwise we waste */ \ - /* lots of time rehashing all the keys over and over. */ \ - sz = h->size; \ - ol = h->table; \ - if (sz < HT_N_INLINE) \ - newsz = HT_N_INLINE; \ - else if (sz >= (1<<19) || (sz <= (1<<8))) \ - newsz = sz<<1; \ - else \ - newsz = sz<<2; \ - /*printf("trying to allocate %d words.\n", newsz); fflush(stdout);*/ \ - tab = (void**)LLT_ALLOC(newsz*sizeof(void*)); \ - if (tab == NULL) \ - return NULL; \ - for(i=0; i < newsz; i++) \ - tab[i] = HT_NOTFOUND; \ - h->table = tab; \ - h->size = newsz; \ - for(i=0; i < sz; i+=2) { \ - if (ol[i+1] != HT_NOTFOUND) { \ - (*HTNAME##_lookup_bp_r(h, ol[i], ctx)) = ol[i+1]; \ + /* table full */ \ + /* quadruple size, rehash, retry the insert */ \ + /* it's important to grow the table really fast; otherwise we waste */ \ + /* lots of time rehashing all the keys over and over. */ \ + sz = h->size; \ + ol = h->table; \ + if (sz < HT_N_INLINE) \ + newsz = HT_N_INLINE; \ + else if (sz >= (1<<19) || (sz <= (1<<8))) \ + newsz = sz<<1; \ + else \ + newsz = sz<<2; \ + /*printf("trying to allocate %d words.\n", newsz); fflush(stdout);*/ \ + tab = (void**)LLT_ALLOC(newsz*sizeof(void*)); \ + if (tab == NULL) \ + return NULL; \ + for (i = 0; i < newsz; i++) \ + tab[i] = HT_NOTFOUND; \ + h->table = tab; \ + h->size = newsz; \ + for (i = 0; i < sz; i += 2) { \ + if (ol[i+1] != HT_NOTFOUND) { \ + (*HTNAME##_lookup_bp_r(h, ol[i], ctx)) = ol[i+1]; \ + } \ } \ - } \ - if (ol != &h->_space[0]) \ - LLT_FREE(ol); \ + if (ol != &h->_space[0]) \ + LLT_FREE(ol); \ \ - sz = hash_size(h); \ - maxprobe = max_probe(sz); \ - tab = h->table; \ - \ - goto retry_bp; \ + sz = hash_size(h); \ + maxprobe = max_probe(sz); \ + tab = h->table; \ + } \ \ return NULL; \ } \ diff --git a/src/task.c b/src/task.c index 1acdb4d4e9794..0b59dec4c770d 100644 --- a/src/task.c +++ b/src/task.c @@ -561,7 +561,10 @@ static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_M ptls->io_wait = 0; // @time needs its compile timer disabled on error, // and cannot use a try-finally as it would break scope for assignments - jl_measure_compile_time[ptls->tid] = 0; + // We blindly disable compilation time tracking here, for all running Tasks, even though + // it may cause some incorrect measurements. This is a known bug, and is being tracked + // here: https://github.com/JuliaLang/julia/pull/39138 + jl_atomic_store_relaxed(&jl_measure_compile_time_enabled, 0); JL_GC_PUSH1(&exception); jl_gc_unsafe_enter(ptls); if (exception) { diff --git a/src/threading.c b/src/threading.c index 235bb9f870ba1..ffe53c07b45ee 100644 --- a/src/threading.c +++ b/src/threading.c @@ -287,8 +287,8 @@ void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k) #endif jl_ptls_t *jl_all_tls_states JL_GLOBALLY_ROOTED; -uint8_t *jl_measure_compile_time = NULL; -uint64_t *jl_cumulative_compile_time = NULL; +uint8_t jl_measure_compile_time_enabled = 0; +uint64_t jl_cumulative_compile_time = 0; // return calling thread's ID // Also update the suspended_threads list in signals-mach when changing the @@ -467,8 +467,6 @@ void jl_init_threading(void) } if (jl_n_threads <= 0) jl_n_threads = 1; - jl_measure_compile_time = (uint8_t*)calloc(jl_n_threads, sizeof(*jl_measure_compile_time)); - jl_cumulative_compile_time = (uint64_t*)calloc(jl_n_threads, sizeof(*jl_cumulative_compile_time)); #ifndef __clang_analyzer__ jl_all_tls_states = (jl_ptls_t*)calloc(jl_n_threads, sizeof(void*)); #endif diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 6f8ba9ea0b080..b116216acc80c 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -34,40 +34,40 @@ The memory consumption estimate is an approximate lower bound on the size of the - `sortby` : the column to sort results by. Options are `:name` (default), `:size`, and `:summary`. """ function varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported::Bool = false, sortby::Symbol = :name, recursive::Bool = false) - @assert sortby in [:name, :size, :summary] "Unrecognized `sortby` value `:$sortby`. Possible options are `:name`, `:size`, and `:summary`" - function _populate_rows(m2::Module, allrows, include_self::Bool, prep::String) - newrows = Any[ - let - value = getfield(m2, v) - ssize_str, ssize = if value===Base || value===Main || value===Core + sortby in (:name, :size, :summary) || throw(ArgumentError("Unrecognized `sortby` value `:$sortby`. Possible options are `:name`, `:size`, and `:summary`")) + rows = Vector{Any}[] + workqueue = [(m, ""),] + while !isempty(workqueue) + m2, prep = popfirst!(workqueue) + for v in names(m2; all, imported) + if !isdefined(m2, v) || !occursin(pattern, string(v)) + continue + end + value = getfield(m2, v) + isbuiltin = value === Base || value === Main || value === Core + if recursive && !isbuiltin && isa(value, Module) && value !== m2 && nameof(value) === v && parentmodule(value) === m2 + push!(workqueue, (value, "$prep$v.")) + end + ssize_str, ssize = if isbuiltin ("", typemax(Int)) else ss = summarysize(value) (format_bytes(ss), ss) end - Any[string(prep, v), ssize_str, summary(value), ssize] - end - for v in names(m2; all, imported) - if (string(v) != split(string(m2), ".")[end] || include_self) && isdefined(m2, v) && occursin(pattern, string(v)) ] - append!(allrows, newrows) - if recursive - for row in newrows - if row[3] == "Module" && !in(split(row[1], ".")[end], [split(string(m2), ".")[end], "Base", "Main", "Core"]) - _populate_rows(getfield(m2, Symbol(split(row[1], ".")[end])), allrows, false, prep * "$(row[1]).") - end - end + push!(rows, Any[string(prep, v), ssize_str, summary(value), ssize]) end - return allrows end - rows = _populate_rows(m, Vector{Any}[], true, "") - if sortby == :name - col, reverse = 1, false - elseif sortby == :size - col, reverse = 4, true - elseif sortby == :summary - col, reverse = 3, false + let (col, rev) = if sortby == :name + 1, false + elseif sortby == :size + 4, true + elseif sortby == :summary + 3, false + else + @assert "unreachable" + end + sort!(rows; by=r->r[col], rev) end - rows = sort!(rows, by=r->r[col], rev=reverse) pushfirst!(rows, Any["name", "size", "summary"]) return Markdown.MD(Any[Markdown.Table(map(r->r[1:3], rows), Symbol[:l, :r, :l])]) @@ -208,54 +208,35 @@ function methodswith(t::Type; supertypes::Bool=false) end # subtypes -function _subtypes(m::Module, x::Type, sts=Base.IdSet{Any}(), visited=Base.IdSet{Module}()) - push!(visited, m) +function _subtypes_in!(mods::Array, x::Type) xt = unwrap_unionall(x) - if !isa(xt, DataType) - return sts + if !isabstracttype(x) || !isa(xt, DataType) + # Fast path + return Type[] end - xt = xt::DataType - for s in names(m, all = true) - if isdefined(m, s) && !isdeprecated(m, s) - t = getfield(m, s) - if isa(t, DataType) - t = t::DataType - if t.name.name === s && supertype(t).name == xt.name - ti = typeintersect(t, x) - ti != Bottom && push!(sts, ti) - end - elseif isa(t, UnionAll) - t = t::UnionAll - tt = unwrap_unionall(t) - isa(tt, DataType) || continue - tt = tt::DataType - if tt.name.name === s && supertype(tt).name == xt.name - ti = typeintersect(t, x) - ti != Bottom && push!(sts, ti) + sts = Vector{Any}() + while !isempty(mods) + m = pop!(mods) + xt = xt::DataType + for s in names(m, all = true) + if isdefined(m, s) && !isdeprecated(m, s) + t = getfield(m, s) + dt = isa(t, UnionAll) ? unwrap_unionall(t) : t + if isa(dt, DataType) + if dt.name.name === s && dt.name.module == m && supertype(dt).name == xt.name + ti = typeintersect(t, x) + ti != Bottom && push!(sts, ti) + end + elseif isa(t, Module) && nameof(t) === s && parentmodule(t) === m && t !== m + t === Base || push!(mods, t) # exclude Base, since it also parented by Main end - elseif isa(t, Module) - t = t::Module - in(t, visited) || _subtypes(t, x, sts, visited) end end end - return sts -end - -function _subtypes_in(mods::Array, x::Type) - if !isabstracttype(x) - # Fast path - return Type[] - end - sts = Base.IdSet{Any}() - visited = Base.IdSet{Module}() - for m in mods - _subtypes(m, x, sts, visited) - end - return sort!(collect(sts), by=string) + return permute!(sts, sortperm(map(string, sts))) end -subtypes(m::Module, x::Type) = _subtypes_in([m], x) +subtypes(m::Module, x::Type) = _subtypes_in!([m], x) """ subtypes(T::DataType) @@ -274,7 +255,7 @@ julia> subtypes(Integer) Unsigned ``` """ -subtypes(x::Type) = _subtypes_in(Base.loaded_modules_array(), x) +subtypes(x::Type) = _subtypes_in!(Base.loaded_modules_array(), x) """ supertypes(T::Type) diff --git a/stdlib/LinearAlgebra/src/blas.jl b/stdlib/LinearAlgebra/src/blas.jl index 327beb020901b..c96fc9b965b1e 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -79,7 +79,7 @@ import LinearAlgebra: BlasReal, BlasComplex, BlasFloat, BlasInt, DimensionMismat include("lbt.jl") """ -get_config() + get_config() Return an object representing the current `libblastrampoline` configuration. diff --git a/stdlib/LinearAlgebra/src/lbt.jl b/stdlib/LinearAlgebra/src/lbt.jl index 67ce521a9aa7e..b1a2dc24b3449 100644 --- a/stdlib/LinearAlgebra/src/lbt.jl +++ b/stdlib/LinearAlgebra/src/lbt.jl @@ -17,6 +17,7 @@ const LBT_INTERFACE_MAP = Dict( LBT_INTERFACE_ILP64 => :ilp64, LBT_INTERFACE_UNKNOWN => :unknown, ) +const LBT_INV_INTERFACE_MAP = Dict(v => k for (k, v) in LBT_INTERFACE_MAP) const LBT_F2C_PLAIN = 0 const LBT_F2C_REQUIRED = 1 @@ -26,6 +27,7 @@ const LBT_F2C_MAP = Dict( LBT_F2C_REQUIRED => :required, LBT_F2C_UNKNOWN => :unknown, ) +const LBT_INV_F2C_MAP = Dict(v => k for (k, v) in LBT_F2C_MAP) struct LBTLibraryInfo libname::String @@ -164,14 +166,74 @@ function lbt_get_default_func() return ccall((:lbt_get_default_func, libblastrampoline), Ptr{Cvoid}, ()) end -#= -Don't define footgun API (yet) +""" + lbt_find_backing_library(symbol_name, interface; config::LBTConfig = lbt_get_config()) -function lbt_get_forward(symbol_name, interface, f2c = LBT_F2C_PLAIN) - return ccall((:lbt_get_forward, libblastrampoline), Ptr{Cvoid}, (Cstring, Int32, Int32), symbol_name, interface, f2c) +Return the `LBTLibraryInfo` that represents the backing library for the given symbol +exported from libblastrampoline. This allows us to discover which library will service +a particular BLAS call from Julia code. This method returns `nothing` if either of the +following conditions are met: + + * No loaded library exports the desired symbol (the default function will be called) + * The symbol was set via `lbt_set_forward()`, which does not track library provenance. + +If the given `symbol_name` is not contained within the list of exported symbols, an +`ArgumentError` will be thrown. +""" +function lbt_find_backing_library(symbol_name, interface::Symbol; + config::LBTConfig = lbt_get_config()) + if interface ∉ (:ilp64, :lp64) + throw(Argument("Invalid interface specification: '$(interface)'")) + end + symbol_idx = findfirst(s -> s == symbol_name, config.exported_symbols) + if symbol_idx === nothing + throw(ArgumentError("Invalid exported symbol name '$(symbol_name)'")) + end + # Convert to zero-indexed + symbol_idx -= 1 + + forward_byte_offset = div(symbol_idx, 8) + forward_byte_mask = 1 << mod(symbol_idx, 8) + for lib in filter(l -> l.interface == interface, config.loaded_libs) + if lib.active_forwards[forward_byte_offset+1] & forward_byte_mask != 0x00 + return lib + end + end + + # No backing library was found + return nothing end + +## NOTE: Manually setting forwards is referred to as the 'footgun API'. It allows truly +## bizarre and complex setups to be created. If you run into strange errors while using +## it, the first thing you should ask yourself is whether you've set things up properly. function lbt_set_forward(symbol_name, addr, interface, f2c = LBT_F2C_PLAIN; verbose::Bool = false) - return ccall((:lbt_set_forward, libblastrampoline), Int32, (Cstring, Ptr{Cvoid}, Int32, Int32, Int32), symbol_name, addr, interface, f2c, verbose ? 1 : 0) + return ccall( + (:lbt_set_forward, libblastrampoline), + Int32, + (Cstring, Ptr{Cvoid}, Int32, Int32, Int32), + string(symbol_name), + addr, + Int32(interface), + Int32(f2c), + verbose ? Int32(1) : Int32(0), + ) +end +function lbt_set_forward(symbol_name, addr, interface::Symbol, f2c::Symbol = :plain; kwargs...) + return lbt_set_forward(symbol_name, addr, LBT_INV_INTERFACE_MAP[interface], LBT_INV_F2C_MAP[f2c]; kwargs...) +end + +function lbt_get_forward(symbol_name, interface, f2c = LBT_F2C_PLAIN) + return ccall( + (:lbt_get_forward, libblastrampoline), + Ptr{Cvoid}, + (Cstring, Int32, Int32), + string(symbol_name), + Int32(interface), + Int32(f2c), + ) +end +function lbt_get_forward(symbol_name, interface::Symbol, f2c::Symbol = :plain) + return lbt_get_forward(symbol_name, LBT_INV_INTERFACE_MAP[interface], LBT_INV_F2C_MAP[f2c]) end -=# \ No newline at end of file diff --git a/stdlib/Markdown/src/Common/inline.jl b/stdlib/Markdown/src/Common/inline.jl index d2855f27a7add..fd5134481e113 100644 --- a/stdlib/Markdown/src/Common/inline.jl +++ b/stdlib/Markdown/src/Common/inline.jl @@ -146,13 +146,10 @@ function _is_link(s::AbstractString) end # non-normative regex from the HTML5 spec -const _email_regex = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" +const _email_regex = r"^mailto\:[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" function _is_mailto(s::AbstractString) - length(s) < 6 && return false - # slicing strings is a bit risky, but this equality check is safe - lowercase(s[1:6]) == "mailto:" || return false - return occursin(_email_regex, s[6:end]) + return occursin(_email_regex, s) end # ––––––––––– diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index f90eefb85310e..3de9e667e2e06 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -1230,3 +1230,11 @@ end @test sprint(show, MIME("text/plain"), s) == " Misc:\n - line" end +@testset "issue #42139: autolink" begin + # ok + @test md"" |> html == """

mailto:foo@bar.com

\n""" + # not ok + @test md"" |> html == """

<mailto foo@bar.com>

\n""" + # see issue #42139 + @test md"<一轮红日初升>" |> html == """

<一轮红日初升>

\n""" +end diff --git a/stdlib/OpenBLAS_jll/Project.toml b/stdlib/OpenBLAS_jll/Project.toml index f7d4dcce97cd7..ac19d703cd523 100644 --- a/stdlib/OpenBLAS_jll/Project.toml +++ b/stdlib/OpenBLAS_jll/Project.toml @@ -1,6 +1,6 @@ name = "OpenBLAS_jll" uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.13+6" +version = "0.3.13+7" [deps] CompilerSupportLibraries_jll = "e66e0078-7015-5450-92f7-15fbd957f2ae" diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index 937bae89a12d2..6b2533e141938 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,2 +1,2 @@ PKG_BRANCH = release-1.7 -PKG_SHA1 = df2476a01c01cdadd553dbf010b21fabe7e211d3 +PKG_SHA1 = 9f30b81e367d7fed6ae13577cd8a434fc30e625e diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index e3813af5dddca..b38298d35aceb 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -422,27 +422,31 @@ const __BIG_FLOAT_MAX__ = 8192 elseif T == Val{'f'} || T == Val{'F'} newpos = Ryu.writefixed(buf, pos, x, prec, plus, space, hash, UInt8('.')) elseif T == Val{'g'} || T == Val{'G'} - # C11-compliant general format - prec = prec == 0 ? 1 : prec - # format the value in scientific notation and parse the exponent part - exp = let p = Ryu.writeexp(buf, pos, x, prec) - b1, b2, b3, b4 = buf[p-4], buf[p-3], buf[p-2], buf[p-1] - Z = UInt8('0') - if b1 == UInt8('e') - # two-digit exponent - sign = b2 == UInt8('+') ? 1 : -1 - exp = 10 * (b3 - Z) + (b4 - Z) + if isinf(x) || isnan(x) + newpos = Ryu.writeshortest(buf, pos, x, plus, space) + else + # C11-compliant general format + prec = prec == 0 ? 1 : prec + # format the value in scientific notation and parse the exponent part + exp = let p = Ryu.writeexp(buf, pos, x, prec) + b1, b2, b3, b4 = buf[p-4], buf[p-3], buf[p-2], buf[p-1] + Z = UInt8('0') + if b1 == UInt8('e') + # two-digit exponent + sign = b2 == UInt8('+') ? 1 : -1 + exp = 10 * (b3 - Z) + (b4 - Z) + else + # three-digit exponent + sign = b1 == UInt8('+') ? 1 : -1 + exp = 100 * (b2 - Z) + 10 * (b3 - Z) + (b4 - Z) + end + flipsign(exp, sign) + end + if -4 ≤ exp < prec + newpos = Ryu.writefixed(buf, pos, x, prec - (exp + 1), plus, space, hash, UInt8('.'), !hash) else - # three-digit exponent - sign = b1 == UInt8('+') ? 1 : -1 - exp = 100 * (b2 - Z) + 10 * (b3 - Z) + (b4 - Z) + newpos = Ryu.writeexp(buf, pos, x, prec - 1, plus, space, hash, T == Val{'g'} ? UInt8('e') : UInt8('E'), UInt8('.'), !hash) end - flipsign(exp, sign) - end - if -4 ≤ exp < prec - newpos = Ryu.writefixed(buf, pos, x, prec - (exp + 1), plus, space, hash, UInt8('.'), !hash) - else - newpos = Ryu.writeexp(buf, pos, x, prec - 1, plus, space, hash, T == Val{'g'} ? UInt8('e') : UInt8('E'), UInt8('.'), !hash) end elseif T == Val{'a'} || T == Val{'A'} x, neg = x < 0 || x === -Base.zero(x) ? (-x, true) : (x, false) diff --git a/stdlib/Printf/test/runtests.jl b/stdlib/Printf/test/runtests.jl index f1438b0a0f2f1..5490bbf70e930 100644 --- a/stdlib/Printf/test/runtests.jl +++ b/stdlib/Printf/test/runtests.jl @@ -94,6 +94,15 @@ end @test Printf.@sprintf("%g", 123456.7) == "123457" @test Printf.@sprintf("%g", 1234567.8) == "1.23457e+06" + # %g regression gh #41631 + for (val, res) in ((Inf, "Inf"), + (-Inf, "-Inf"), + (NaN, "NaN"), + (-NaN, "NaN")) + @test Printf.@sprintf("%g", val) == res + @test Printf.@sprintf("%G", val) == res + end + # zeros @test Printf.@sprintf("%.15g", 0) == "0" @test Printf.@sprintf("%#.15g", 0) == "0.00000000000000" diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index a661ffa218e97..289b927c44212 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -167,6 +167,7 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend) end function check_for_missing_packages_and_run_hooks(ast) + isa(ast, Expr) || return mods = modules_to_be_loaded(ast) filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules if !isempty(mods) @@ -176,15 +177,18 @@ function check_for_missing_packages_and_run_hooks(ast) end end -function modules_to_be_loaded(ast, mods = Symbol[]) +function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) + ast.head == :quote && return mods # don't search if it's not going to be run during this eval if ast.head in [:using, :import] for arg in ast.args - if first(arg.args) isa Symbol # i.e. `Foo` - if first(arg.args) != :. # don't include local imports - push!(mods, first(arg.args)) + arg = arg::Expr + arg1 = first(arg.args) + if arg1 isa Symbol # i.e. `Foo` + if arg1 != :. # don't include local imports + push!(mods, arg1) end else # i.e. `Foo: bar` - push!(mods, first(first(arg.args).args)) + push!(mods, first((arg1::Expr).args)) end end end @@ -192,9 +196,8 @@ function modules_to_be_loaded(ast, mods = Symbol[]) arg isa Expr && modules_to_be_loaded(arg, mods) end filter!(mod -> !in(String(mod), ["Base", "Main", "Core"]), mods) # Exclude special non-package modules - return mods + return unique(mods) end -modules_to_be_loaded(::Nothing) = Symbol[] # comments are parsed as nothing """ start_repl_backend(repl_channel::Channel, response_channel::Channel) diff --git a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl index ee5bd4d426795..f01df5c389324 100644 --- a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl @@ -203,9 +203,9 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio lastoption = numoptions(m) c = readkey(term.in_stream) - if c == Int(ARROW_UP) || c == Int('k') + if c == Int(ARROW_UP) cursor[] = move_up!(m, cursor[], lastoption) - elseif c == Int(ARROW_DOWN) || c == Int('j') + elseif c == Int(ARROW_DOWN) cursor[] = move_down!(m, cursor[], lastoption) elseif c == Int(PAGE_UP) cursor[] = page_up!(m, cursor[], lastoption) @@ -217,7 +217,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio elseif c == Int(END_KEY) cursor[] = lastoption m.pageoffset = lastoption - m.pagesize - elseif c == 13 || c == Int(' ') # or + elseif c == 13 # # will break if pick returns true pick(m, cursor[]) && break elseif c == UInt32('q') diff --git a/stdlib/REPL/test/TerminalMenus/runtests.jl b/stdlib/REPL/test/TerminalMenus/runtests.jl index ac577dfd9ab27..62a91cc0a1256 100644 --- a/stdlib/REPL/test/TerminalMenus/runtests.jl +++ b/stdlib/REPL/test/TerminalMenus/runtests.jl @@ -6,22 +6,10 @@ using Test function simulate_input(expected, menu::TerminalMenus.AbstractMenu, keys...; kwargs...) - keydict = Dict(:up => "\e[A", - :down => "\e[B", - :enter => "\r") - vimdict = Dict(:up => "k", - :down => "j", - :enter => " ") - errs = [] - got = _simulate_input(keydict, deepcopy(menu), keys...; kwargs...) - got == expected || push!(errs, :arrows => got) - got = _simulate_input(vimdict, menu, keys...; kwargs...) - got == expected || push!(errs, :vim => got) - isempty(errs) || return errs -end + keydict = Dict(:up => "\e[A", + :down => "\e[B", + :enter => "\r") -function _simulate_input(keydict, menu::TerminalMenus.AbstractMenu, keys...; - kwargs...) for key in keys if isa(key, Symbol) write(stdin.buffer, keydict[key]) @@ -30,7 +18,7 @@ function _simulate_input(keydict, menu::TerminalMenus.AbstractMenu, keys...; end end - request(menu; suppress_output=true, kwargs...) + request(menu; suppress_output=true, kwargs...) == expected end include("radio_menu.jl") diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 8c4ee75850fbf..3fbf6d8825bba 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1332,6 +1332,8 @@ end @test mods == [:Foo, :Bar] mods = REPL.modules_to_be_loaded(Base.parse_input_line("import Foo, Bar")) @test mods == [:Foo, :Bar] + mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Foo.bar, Foo.baz")) + @test mods == [:Foo] mods = REPL.modules_to_be_loaded(Base.parse_input_line("if false using Foo end")) @test mods == [:Foo] @@ -1355,8 +1357,11 @@ end mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Core")) @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("# comment")) + mods = REPL.modules_to_be_loaded(Base.parse_input_line(":(using Foo)")) + @test isempty(mods) + mods = REPL.modules_to_be_loaded(Base.parse_input_line("ex = :(using Foo)")) @test isempty(mods) + mods = REPL.modules_to_be_loaded(Base.parse_input_line("Foo")) @test isempty(mods) end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 461c48026944e..c916501dc2543 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -706,7 +706,7 @@ function do_test_throws(result::ExecutionResult, orig_expr, extype) if isa(extype, Type) success = if from_macroexpand && extype == LoadError && exc isa Exception - Base.depwarn("macroexpand no longer throw a LoadError so `@test_throws LoadError ...` is deprecated and passed without checking the error type!", :do_test_throws) + Base.depwarn("macroexpand no longer throws a LoadError so `@test_throws LoadError ...` is deprecated and passed without checking the error type!", :do_test_throws) true else isa(exc, extype) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 265d97776c053..0516d9a74e436 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -66,7 +66,7 @@ end ## Other ways of accessing functions # Test that non-ambiguous cases work let io = IOBuffer() - @test @test_logs precompile(ambig, (Int, Int)) + @test precompile(ambig, (Int, Int)) cf = @eval @cfunction(ambig, Int, (Int, Int)) @test ccall(cf, Int, (Int, Int), 1, 2) == 4 @test length(code_lowered(ambig, (Int, Int))) == 1 @@ -75,7 +75,7 @@ end # Test that ambiguous cases fail appropriately let io = IOBuffer() - @test @test_logs (:warn,) precompile(ambig, (UInt8, Int)) + @test !precompile(ambig, (UInt8, Int)) cf = @eval @cfunction(ambig, Int, (UInt8, Int)) # test for a crash (doesn't throw an error) @test_throws(MethodError(ambig, (UInt8(1), Int(2)), get_world_counter()), ccall(cf, Int, (UInt8, Int), 1, 2)) diff --git a/test/atomics.jl b/test/atomics.jl index 4c32fc12d87ed..c53471ed0da26 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -4,6 +4,8 @@ using Test, Base.Threads using Core: ConcurrencyViolationError import Base: copy +const ReplaceType = ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T + mutable struct ARefxy{T} @atomic x::T y::T @@ -86,17 +88,18 @@ Base.show(io::IO, x::Int24) = print(io, "Int24(", Core.Intrinsics.zext_int(Int, @noinline function _test_field_operators(r) r = r[] + TT = fieldtype(typeof(r), :x) T = typeof(getfield(r, :x)) @test getfield(r, :x, :sequentially_consistent) === T(123_10) @test setfield!(r, :x, T(123_1), :sequentially_consistent) === T(123_1) @test getfield(r, :x, :sequentially_consistent) === T(123_1) - @test replacefield!(r, :x, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent) === (T(123_1), false) - @test replacefield!(r, :x, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent) === (T(123_1), true) + @test replacefield!(r, :x, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), false)) + @test replacefield!(r, :x, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), true)) @test getfield(r, :x, :sequentially_consistent) === T(123_30) - @test replacefield!(r, :x, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent) === (T(123_30), false) + @test replacefield!(r, :x, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_30), false)) @test getfield(r, :x, :sequentially_consistent) === T(123_30) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(123_30), T(123_31)) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(123_31), T(123_32)) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_30), T(123_31)) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_31), T(123_32)) @test getfield(r, :x, :sequentially_consistent) === T(123_32) @test swapfield!(r, :x, T(123_1), :sequentially_consistent) === T(123_32) @test getfield(r, :x, :sequentially_consistent) === T(123_1) @@ -120,6 +123,7 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @noinline function _test_field_orderings(r, x, y) @nospecialize x y r = r[] + TT = fieldtype(typeof(r), :x) @test getfield(r, :x) === x @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :x, :u) @@ -199,7 +203,7 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :release) @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :acquire_release) @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :sequentially_consistent) - @test modifyfield!(r, :y, swap, x, :not_atomic) === (y, x) + @test modifyfield!(r, :y, swap, x, :not_atomic) === Pair{TT,TT}(y, x) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :u, :not_atomic) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :unordered, :not_atomic) @@ -215,10 +219,10 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :release) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :acquire_release) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :sequentially_consistent) - @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === (x, true) - @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === (y, x === y) - @test replacefield!(r, :y, y, y, :not_atomic) === (y, true) - @test replacefield!(r, :y, y, y) === (y, true) + @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === ReplaceType{TT}((x, true)) + @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === ReplaceType{TT}((y, x === y)) + @test replacefield!(r, :y, y, y, :not_atomic) === ReplaceType{TT}((y, true)) + @test replacefield!(r, :y, y, y) === ReplaceType{TT}((y, true)) @test_throws ConcurrencyViolationError("invalid atomic ordering") swapfield!(r, :x, x, :u) @test_throws ConcurrencyViolationError("swapfield!: atomic field cannot be written non-atomically") swapfield!(r, :x, x, :not_atomic) @@ -234,11 +238,11 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x, :not_atomic) @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x) @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :x, swap, x, :unordered) - @test modifyfield!(r, :x, swap, x, :monotonic) === (x, x) - @test modifyfield!(r, :x, swap, x, :acquire) === (x, x) - @test modifyfield!(r, :x, swap, x, :release) === (x, x) - @test modifyfield!(r, :x, swap, x, :acquire_release) === (x, x) - @test modifyfield!(r, :x, swap, x, :sequentially_consistent) === (x, x) + @test modifyfield!(r, :x, swap, x, :monotonic) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :acquire) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :release) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :acquire_release) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :sequentially_consistent) === Pair{TT,TT}(x, x) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :u, :not_atomic) @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x) @@ -256,9 +260,9 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :release) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :acquire_release) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :sequentially_consistent) - @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (x, true) - @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (y, x === y) - @test replacefield!(r, :x, y, x, :sequentially_consistent) === (y, true) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((x, true)) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((y, x === y)) + @test replacefield!(r, :x, y, x, :sequentially_consistent) === ReplaceType{TT}((y, true)) nothing end @noinline function test_field_orderings(r, x, y) @@ -339,10 +343,10 @@ let a = ARefxy(1, -1) @test 12 === @atomic :monotonic a.x *= 3 @test 12 === @atomic a.x - @test (12, 13) === @atomic a.x + 1 - @test (13, 15) === @atomic :monotonic a.x + 2 - @test (15, 19) === @atomic a.x max 19 - @test (19, 20) === @atomic :monotonic a.x max 20 + @test (12 => 13) === @atomic a.x + 1 + @test (13 => 15) === @atomic :monotonic a.x + 2 + @test (15 => 19) === @atomic a.x max 19 + @test (19 => 20) === @atomic :monotonic a.x max 20 @test_throws ConcurrencyViolationError @atomic :not_atomic a.x + 1 @test_throws ConcurrencyViolationError @atomic :not_atomic a.x max 30 @@ -352,17 +356,17 @@ let a = ARefxy(1, -1) @test_throws ConcurrencyViolationError @atomicswap :not_atomic a.x = 1 @test 2 === @atomic a.x - @test (2, true) === @atomicreplace a.x 2 => 1 - @test (1, false) === @atomicreplace :monotonic a.x 2 => 1 - @test (1, false) === @atomicreplace :monotonic :monotonic a.x 2 => 1 + @test ReplaceType{Int}((2, true)) === @atomicreplace a.x 2 => 1 + @test ReplaceType{Int}((1, false)) === @atomicreplace :monotonic a.x 2 => 1 + @test ReplaceType{Int}((1, false)) === @atomicreplace :monotonic :monotonic a.x 2 => 1 @test_throws ConcurrencyViolationError @atomicreplace :not_atomic a.x 1 => 2 @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x 1 => 2 @test 1 === @atomic a.x xchg = 1 => 2 - @test (1, true) === @atomicreplace a.x xchg - @test (2, false) === @atomicreplace :monotonic a.x xchg - @test (2, false) === @atomicreplace :acquire_release :monotonic a.x xchg + @test ReplaceType{Int}((1, true)) === @atomicreplace a.x xchg + @test ReplaceType{Int}((2, false)) === @atomicreplace :monotonic a.x xchg + @test ReplaceType{Int}((2, false)) === @atomicreplace :acquire_release :monotonic a.x xchg @test_throws ConcurrencyViolationError @atomicreplace :not_atomic a.x xchg @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x xchg end diff --git a/test/ccall.jl b/test/ccall.jl index 02d005108459e..01f0f4f651aa8 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -982,6 +982,26 @@ for (t, v) in ((Complex{Int32}, :ci32), (Complex{Int64}, :ci64), end end + +#issue 40164 +@testset "llvm parameter attributes on cfunction closures" begin + struct Struct40164 + x::Cdouble + y::Cdouble + z::Cdouble + end + + function test_40164() + ret = Struct40164[] + f = x::Struct40164 -> (push!(ret, x); nothing) + f_c = @cfunction($f, Cvoid, (Struct40164,)) + ccall(f_c.ptr, Ptr{Cvoid}, (Struct40164,), Struct40164(0, 1, 2)) + ret + end + + @test test_40164() == [Struct40164(0, 1, 2)] +end + else @test_broken "cfunction: no support for closures on this platform" diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index c5e82b681a3c4..fb206acf03477 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -599,7 +599,7 @@ end # test error handling code paths of running --sysimage -let exename = Base.julia_cmd() +let exename = `$(Base.julia_cmd().exec[1]) -t 1` sysname = unsafe_string(Base.JLOptions().image_file) for nonexist_image in ( joinpath(@__DIR__, "nonexistent"), diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index cc6095b867d41..7c89261bea92b 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -594,6 +594,14 @@ f41438(y) = y[].x @test f41438(Ref{A41438}(A41438(C_NULL))) === C_NULL @test f41438(Ref{B41438}(B41438(C_NULL))) === C_NULL +const S41438 = Pair{Any, Ptr{T}} where T +g41438() = Array{S41438,1}(undef,1)[1].first +get_llvm(g41438, ()); # cause allocation of layout +@test S41438.body.layout != C_NULL +@test !Base.datatype_pointerfree(S41438.body) +@test S41438{Int}.layout != C_NULL +@test !Base.datatype_pointerfree(S41438{Int}) + # issue #41157 f41157(a, b) = a[1] = b[1] @test_throws BoundsError f41157(Tuple{Int}[], Tuple{Union{}}[]) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 4d7aed372f6c8..d4d0f6700c179 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -44,6 +44,11 @@ let t = Tuple{Ref{T},T,T} where T, c = Tuple{Ref, T, T} where T # #36407 @test t <: Core.Compiler.limit_type_size(t, c, Union{}, 1, 100) end +# obtain Vararg with 2 undefined fields +let va = ccall(:jl_type_intersection_with_env, Any, (Any, Any), Tuple{Tuple}, Tuple{Tuple{Vararg{Any, N}}} where N)[2][1] + @test Core.Compiler.limit_type_size(Tuple, va, Union{}, 2, 2) === Any +end + let # 40336 t = Type{Type{Int}} c = Type{Int} @@ -1823,16 +1828,39 @@ end return c, d # ::Tuple{Int,Int} end == Any[Tuple{Int,Int}] - # shouldn't use the old constraint when the subject of condition has changed + # should invalidate old constraint when the subject of condition has changed @test Base.return_types((Union{Nothing,Int},)) do a - b = a === nothing - c = b ? 0 : a # c::Int + cond = a === nothing + r1 = cond ? 0 : a # r1::Int a = 0 - d = b ? a : 1 # d::Int, not d::Union{Nothing,Int} - return c, d # ::Tuple{Int,Int} + r2 = cond ? a : 1 # r2::Int, not r2::Union{Nothing,Int} + return r1, r2 # ::Tuple{Int,Int} end == Any[Tuple{Int,Int}] end +# https://github.com/JuliaLang/julia/issues/42090#issuecomment-911824851 +# `PartialStruct` shoudln't wrap `Conditional` +let M = Module() + @eval M begin + struct BePartialStruct + val::Int + cond + end + end + + rt = @eval M begin + Base.return_types((Union{Nothing,Int},)) do a + cond = a === nothing + 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) + return r1, r2 # ::Tuple{Union{Nothing,Int},Any} + end |> only + end + @test rt == Tuple{Union{Nothing,Int},Any} +end + @testset "conditional constraint propagation from non-`Conditional` object" begin @test Base.return_types((Bool,)) do b if b @@ -2210,12 +2238,10 @@ code28279 = code_lowered(f28279, (Bool,))[1].code oldcode28279 = deepcopy(code28279) ssachangemap = fill(0, length(code28279)) labelchangemap = fill(0, length(code28279)) -worklist = Int[] let i for i in 1:length(code28279) stmt = code28279[i] if isa(stmt, GotoIfNot) - push!(worklist, i) ssachangemap[i] = 1 if i < length(code28279) labelchangemap[i + 1] = 1 @@ -2875,9 +2901,24 @@ partial_return_2(x) = Val{partial_return_1(x)[2]} @test Base.return_types(partial_return_2, (Int,)) == Any[Type{Val{1}}] -# Precision of abstract_iteration +# Soundness and precision of abstract_iteration +f41839() = (1:100...,) +@test NTuple{100,Int} <: only(Base.return_types(f41839, ())) <: Tuple{Vararg{Int}} f_splat(x) = (x...,) @test Base.return_types(f_splat, (Pair{Int,Int},)) == Any[Tuple{Int, Int}] +@test Base.return_types(f_splat, (UnitRange{Int},)) == Any[Tuple{Vararg{Int}}] +struct Itr41839_1 end # empty or infinite +Base.iterate(::Itr41839_1) = rand(Bool) ? (nothing, nothing) : nothing +Base.iterate(::Itr41839_1, ::Nothing) = (nothing, nothing) +@test Base.return_types(f_splat, (Itr41839_1,)) == Any[Tuple{}] +struct Itr41839_2 end # empty or failing +Base.iterate(::Itr41839_2) = rand(Bool) ? (nothing, nothing) : nothing +Base.iterate(::Itr41839_2, ::Nothing) = error() +@test Base.return_types(f_splat, (Itr41839_2,)) == Any[Tuple{}] +struct Itr41839_3 end +Base.iterate(::Itr41839_3 ) = rand(Bool) ? nothing : (nothing, 1) +Base.iterate(::Itr41839_3 , i) = i < 16 ? (i, i + 1) : nothing +@test only(Base.return_types(f_splat, (Itr41839_3,))) <: Tuple{Vararg{Union{Nothing, Int}}} # issue #32699 f32699(a) = (id = a[1],).id @@ -3406,3 +3447,60 @@ end @test @inferred(f40177(T)) == fieldtype(T, 1) end end + +# issue #41908 +f41908(x::Complex{T}) where {String<:T<:String} = 1 +g41908() = f41908(Any[1][1]) +@test only(Base.return_types(g41908, ())) <: Int + +# issue #42022 +let x = Tuple{Int,Any}[ + #= 1=# (0, Expr(:(=), Core.SlotNumber(3), 1)) + #= 2=# (0, Expr(:enter, 18)) + #= 3=# (2, Expr(:(=), Core.SlotNumber(3), 2.0)) + #= 4=# (2, Expr(:enter, 12)) + #= 5=# (4, Expr(:(=), Core.SlotNumber(3), '3')) + #= 6=# (4, Core.GotoIfNot(Core.SlotNumber(2), 9)) + #= 7=# (4, Expr(:leave, 2)) + #= 8=# (0, Core.ReturnNode(1)) + #= 9=# (4, Expr(:call, GlobalRef(Main, :throw))) + #=10=# (4, Expr(:leave, 1)) + #=11=# (2, Core.GotoNode(16)) + #=12=# (4, Expr(:leave, 1)) + #=13=# (2, Expr(:(=), Core.SlotNumber(4), Expr(:the_exception))) + #=14=# (2, Expr(:call, GlobalRef(Main, :rethrow))) + #=15=# (2, Expr(:pop_exception, Core.SSAValue(4))) + #=16=# (2, Expr(:leave, 1)) + #=17=# (0, Core.GotoNode(22)) + #=18=# (2, Expr(:leave, 1)) + #=19=# (0, Expr(:(=), Core.SlotNumber(5), Expr(:the_exception))) + #=20=# (0, nothing) + #=21=# (0, Expr(:pop_exception, Core.SSAValue(2))) + #=22=# (0, Core.ReturnNode(Core.SlotNumber(3))) + ] + handler_at = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) + @test handler_at == first.(x) +end + +@test only(Base.return_types((Bool,)) do y + x = 1 + try + x = 2.0 + try + x = '3' + y ? (return 1) : throw() + catch ex1 + rethrow() + end + catch ex2 + nothing + end + return x + end) === Union{Int, Float64, Char} + +# issue #42097 +struct Foo42097{F} end +Foo42097(f::F, args) where {F} = Foo42097{F}() +Foo42097(A) = Foo42097(Base.inferencebarrier(+), Base.inferencebarrier(1)...) +foo42097() = Foo42097([1]...) +@test foo42097() isa Foo42097{typeof(+)} diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index f90bb71e291d0..17a0753eddc64 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -310,3 +310,7 @@ let cfg = CFG(BasicBlock[ Compiler.domtree_insert_edge!(domtree, cfg.blocks, 1, 3) @test domtree.idoms_bb == Compiler.naive_idoms(cfg.blocks) == [0, 1, 1, 3, 1, 4] end + +# Issue #41975 - SSA conversion drops type check +f_if_typecheck() = (if nothing; end; unsafe_load(Ptr{Int}(0))) +@test_throws TypeError f_if_typecheck() diff --git a/test/core.jl b/test/core.jl index e55705b3e6923..74edc7cddf7f4 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7577,3 +7577,13 @@ const T35130 = Tuple{Vector{Int}, <:Any} end h35130(x) = A35130(Any[x][1]::Vector{T35130}) @test h35130(T35130[([1],1)]) isa A35130 + +# issue #41503 +let S = Tuple{Tuple{Tuple{K, UInt128} where K<:Tuple{Int64}, Int64}}, + T = Tuple{Tuple{Tuple{Tuple{Int64}, UInt128}, Int64}} + @test pointer_from_objref(T) === pointer_from_objref(S) + @test isbitstype(T) +end + +# avoid impossible normalization (don't try to form Tuple{Complex{String}} here) +@test Tuple{Complex{T} where String<:T<:String} == Tuple{Complex{T} where String<:T<:String} diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 7fa8ecb0ebe27..589590cf78d14 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -100,6 +100,8 @@ let f = Core.Intrinsics.ashr_int @test f(Int32(2), -1) == 0 end +const ReplaceType = ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T + # issue #29929 let p = Ptr{Nothing}(0) @test unsafe_store!(p, nothing) === C_NULL @@ -107,9 +109,9 @@ let p = Ptr{Nothing}(0) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === nothing @test Core.Intrinsics.atomic_pointerset(p, nothing, :sequentially_consistent) === p @test Core.Intrinsics.atomic_pointerswap(p, nothing, :sequentially_consistent) === nothing - @test Core.Intrinsics.atomic_pointermodify(p, (i, j) -> j, nothing, :sequentially_consistent) === (nothing, nothing) - @test Core.Intrinsics.atomic_pointerreplace(p, nothing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, true) - @test Core.Intrinsics.atomic_pointerreplace(p, missing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, false) + @test Core.Intrinsics.atomic_pointermodify(p, (i, j) -> j, nothing, :sequentially_consistent) === Pair(nothing, nothing) + @test Core.Intrinsics.atomic_pointerreplace(p, nothing, nothing, :sequentially_consistent, :sequentially_consistent) === ReplaceType{Nothing}((nothing, true)) + @test Core.Intrinsics.atomic_pointerreplace(p, missing, nothing, :sequentially_consistent, :sequentially_consistent) === ReplaceType{Nothing}((nothing, false)) end struct GhostStruct end @@ -189,8 +191,8 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @test_throws ErrorException("atomic_pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) @test_throws ErrorException("atomic_pointerset: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) @test_throws ErrorException("atomic_pointerswap: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerswap(p, T(100), :sequentially_consistent) - @test_throws ErrorException("atomic_pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) - @test_throws ErrorException("atomic_pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) + @test_throws ErrorException("atomic_pointermodify: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) + @test_throws ErrorException("atomic_pointermodify: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) @test_throws ErrorException("atomic_pointerreplace: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerreplace(p, T(100), T(2), :sequentially_consistent, :sequentially_consistent) @test_throws ErrorException("atomic_pointerreplace: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] @@ -199,24 +201,24 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(10) @test Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) === p @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(1) - @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(100), :sequentially_consistent, :sequentially_consistent) === (T(1), true) + @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(100), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(1), true)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(100) - @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(1), :sequentially_consistent, :sequentially_consistent) === (T(100), false) + @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(1), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(100), false)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(100) - @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === (T(100), T(101)) - @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === (T(101), T(102)) + @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === Pair{TT,TT}(T(100), T(101)) + @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === Pair{TT,TT}(T(101), T(102)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(102) @test Core.Intrinsics.atomic_pointerswap(p, T(103), :sequentially_consistent) === T(102) - @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === (T(103), false) + @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(103), false)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(103) end if TT === Any - @test Core.Intrinsics.atomic_pointermodify(p, swap, S(103), :sequentially_consistent) === (T(103), S(103)) + @test Core.Intrinsics.atomic_pointermodify(p, swap, S(103), :sequentially_consistent) === Pair{TT,TT}(T(103), S(103)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === S(103) @test Core.Intrinsics.atomic_pointerset(p, S(1), :sequentially_consistent) === p @test Core.Intrinsics.atomic_pointerswap(p, S(100), :sequentially_consistent) === S(1) - @test Core.Intrinsics.atomic_pointerreplace(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) === (S(100), false) - @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === (S(100), true) + @test Core.Intrinsics.atomic_pointerreplace(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((S(100), false)) + @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((S(100), true)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(2) end end)(TT,) diff --git a/test/iterators.jl b/test/iterators.jl index fb8edcab92209..c7d00c4e7e2e8 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -292,6 +292,15 @@ let (a, b) = (1:3, [4 6; end end +# collect stateful iterator +let + itr = (i+1 for i in Base.Stateful([1,2,3])) + @test collect(itr) == [2, 3, 4] + A = zeros(Int, 0, 0) + itr = (i-1 for i in Base.Stateful(A)) + @test collect(itr) == Int[] # Stateful do not preserve shape +end + # with 1D inputs let a = 1:2, b = 1.0:10.0, @@ -860,4 +869,4 @@ end @test Iterators.peel(1:10)[2] |> collect == 2:10 @test Iterators.peel(x^2 for x in 2:4)[1] == 4 @test Iterators.peel(x^2 for x in 2:4)[2] |> collect == [9, 16] -end \ No newline at end of file +end diff --git a/test/misc.jl b/test/misc.jl index 411135ac63313..94c35c43ffaec 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -263,6 +263,22 @@ function timev_macro_scope() end @test timev_macro_scope() == 1 +before = Base.cumulative_compile_time_ns_before(); + +# exercise concurrent calls to `@time` for reentrant compilation time measurement. +t1 = @async @time begin + sleep(2) + @eval module M ; f(x,y) = x+y ; end + @eval M.f(2,3) +end +t2 = @async begin + sleep(1) + @time 2 + 2 +end + +after = Base.cumulative_compile_time_ns_after(); +@test after >= before; + # interactive utilities struct ambigconvert; end # inject a problematic `convert` method to ensure it still works diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 811d3dd26f509..deeb80f019510 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -795,3 +795,24 @@ end @test Iterators.partition(OffsetArray(reshape(collect(1:9),3,3), (3,3)), 5) |> collect == [1:5,6:9] #OffsetMatrix @test Iterators.partition(IdOffsetRange(2:7,10), 5) |> collect == [12:16,17:17] # IdOffsetRange end + +@testset "issue #41630: replace_ref_begin_end!/@view on offset-like arrays" begin + x = OffsetArray([1 2; 3 4], -10:-9, 9:10) # 2×2 OffsetArray{...} with indices -10:-9×9:10 + + # begin/end with offset indices + @test (@view x[begin, 9])[] == 1 + @test (@view x[-10, end])[] == 2 + @test (@view x[-9, begin])[] == 3 + @test (@view x[end, 10])[] == 4 + @test (@view x[begin, begin])[] == 1 + @test (@view x[begin, end])[] == 2 + @test (@view x[end, begin])[] == 3 + @test (@view x[end, end])[] == 4 + + # nested usages of begin/end + y = OffsetArray([-10, -9], (5,)) + @test (@view x[begin, -y[end]])[] == 1 + @test (@view x[y[begin], end])[] == 2 + @test (@view x[end, -y[end]])[] == 3 + @test (@view x[y[end], end])[] == 4 +end diff --git a/test/show.jl b/test/show.jl index c00dcf523898c..b9f8c45797b35 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1377,6 +1377,11 @@ let m = which(T20332{Int}(), (Int,)), mi = Core.Compiler.specialize_method(m, Tuple{T20332{T}, Int} where T, Core.svec()) # test that this doesn't throw an error @test occursin("MethodInstance for", repr(mi)) + # issue #41928 + str = sprint(mi; context=:color=>true) do io, mi + printstyled(io, mi; color=:light_cyan) + end + @test !occursin("\U1b[0m", str) end @test sprint(show, Main) == "Main" @@ -1769,7 +1774,7 @@ end # spurious binding resolutions show(IOContext(b, :module => TestShowType), Base.Pair) @test !Base.isbindingresolved(TestShowType, :Pair) - @test String(take!(b)) == "Base.Pair" + @test String(take!(b)) == "Core.Pair" show(IOContext(b, :module => TestShowType), Base.Complex) @test Base.isbindingresolved(TestShowType, :Complex) @test String(take!(b)) == "Complex" diff --git a/test/subtype.jl b/test/subtype.jl index 17d2b491003fe..d403716646de0 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -587,7 +587,7 @@ function test_old() @test !(Type{Tuple{Nothing}} <: Tuple{Type{Nothing}}) end -const menagerie = +const easy_menagerie = Any[Bottom, Any, Int, Int8, Integer, Real, Array{Int,1}, AbstractArray{Int,1}, Tuple{Int,Vararg{Integer}}, Tuple{Integer,Vararg{Int}}, Tuple{}, @@ -607,12 +607,14 @@ const menagerie = Array{(@UnionAll T<:Int T), 1}, (@UnionAll T<:Real @UnionAll S<:AbstractArray{T,1} Tuple{T,S}), Union{Int,Ref{Union{Int,Int8}}}, - (@UnionAll T Union{Tuple{T,Array{T,1}}, Tuple{T,Array{Int,1}}}), ] -let new = Any[] - # add variants of each type - for T in menagerie +const hard_menagerie = + Any[(@UnionAll T Union{Tuple{T,Array{T,1}}, Tuple{T,Array{Int,1}}})] + +function add_variants!(types) + new = Any[] + for T in types push!(new, Ref{T}) push!(new, Tuple{T}) push!(new, Tuple{T,T}) @@ -620,9 +622,14 @@ let new = Any[] push!(new, @UnionAll S<:T S) push!(new, @UnionAll S<:T Ref{S}) end - append!(menagerie, new) + append!(types, new) end +add_variants!(easy_menagerie) +add_variants!(hard_menagerie) + +const menagerie = [easy_menagerie; hard_menagerie] + function test_properties() x→y = !x || y ¬T = @UnionAll X>:T Ref{X} @@ -1057,14 +1064,15 @@ function test_intersection() end function test_intersection_properties() - approx = Tuple{Vector{Vector{T}} where T, Vector{Vector{T}} where T} - for T in menagerie - for S in menagerie + for i in eachindex(menagerie) + T = menagerie[i] + for j in eachindex(menagerie) + S = menagerie[j] I = _type_intersect(T,S) I2 = _type_intersect(S,T) @test isequal_type(I, I2) - if I == approx - # TODO: some of these cases give a conservative answer + if i > length(easy_menagerie) || j > length(easy_menagerie) + # TODO: these cases give a conservative answer @test issub(I, T) || issub(I, S) else @test issub(I, T) && issub(I, S) @@ -1569,7 +1577,7 @@ f31082(::Pair{B, C}, ::C, ::C) where {B, C} = 1 Tuple{Type{Val{T}},Int,T} where T) @testintersect(Tuple{Type{Val{T}},Integer,T} where T, Tuple{Type,Int,Integer}, - Tuple{Type{Val{T}},Int,T} where T<:Integer) + Tuple{Type{Val{T}},Int,Integer} where T) @testintersect(Tuple{Type{Val{T}},Integer,T} where T>:Integer, Tuple{Type,Int,Integer}, Tuple{Type{Val{T}},Int,Integer} where T>:Integer) @@ -1796,7 +1804,7 @@ let X1 = Tuple{AlmostLU, Vector{T}} where T, # TODO: the quality of this intersection is not great; for now just test that it # doesn't stack overflow @test I<:X1 || I<:X2 - actual = Tuple{AlmostLU{S, X} where X<:Matrix{S}, Vector{S}} where S<:Union{Float32, Float64} + actual = Tuple{Union{AlmostLU{S, X} where X<:Matrix{S}, AlmostLU{S, <:Matrix}}, Vector{S}} where S<:Union{Float32, Float64} @test I == actual end @@ -1858,7 +1866,7 @@ let A = Tuple{Type{T} where T<:Ref, Ref, Union{T, Union{Ref{T}, T}} where T<:Ref I = typeintersect(A,B) # this was a case where <: disagreed with === (due to a badly-normalized type) @test I == typeintersect(A,B) - @test I == Tuple{Type{T}, Ref{T}, Union{Ref{T}, T}} where T<:Ref + @test I == Tuple{Type{T}, Ref{T}, Ref} where T<:Ref end # issue #39218 @@ -1898,8 +1906,8 @@ end # issue #39948 let A = Tuple{Array{Pair{T, JT} where JT<:Ref{T}, 1} where T, Vector}, I = typeintersect(A, Tuple{Vararg{Vector{T}}} where T) - @test_broken I <: A - @test_broken !Base.has_free_typevars(I) + @test I <: A + @test !Base.has_free_typevars(I) end # issue #8915 @@ -1927,3 +1935,39 @@ let A = Tuple{Ref{T}, Vararg{T}} where T, J = typeintersect(A, C) @test_broken J != Union{} end + +let A = Tuple{Dict{I,T}, I, T} where T where I, + B = Tuple{AbstractDict{I,T}, T, I} where T where I + # TODO: we should probably have I == T here + @test typeintersect(A, B) == Tuple{Dict{I,T}, I, T} where {I, T} +end + +let A = Tuple{UnionAll, Vector{Any}}, + B = Tuple{Type{T}, T} where T<:AbstractArray, + I = typeintersect(A, B) + @test !isconcretetype(I) + @test I == Tuple{Type{T}, Vector{Any}} where T<:AbstractArray +end + +@testintersect(Tuple{Type{Vector{<:T}}, T} where {T<:Integer}, + Tuple{Type{T}, AbstractArray} where T<:Array, + Bottom) + +struct S40{_A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _Z1, _Z2, _Z3, _Z4, _Z5, _Z6, _Z7, _Z8, _Z9, _Z10, _Z11, _Z12, _Z13, _Z14} +end + +@testintersect(Tuple{Type{S40{_A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _Z1, _Z2, _Z3, _Z4, _Z5, _Z6, _Z7, _Z8, _Z9, _Z10, _Z11, _Z12, _Z13, _Z14}} where _Z14 where _Z13 where _Z12 where _Z11 where _Z10 where _Z9 where _Z8 where _Z7 where _Z6 where _Z5 where _Z4 where _Z3 where _Z2 where _Z1 where _Z where _Y where _X where _W where _V where _U where _T where _S where _R where _Q where _P where _O where _N where _M where _L where _K where _J where _I where _H where _G where _F where _E where _D where _C where _B where _A, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any}, + Tuple{Type{S40{A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40} where A40 where A39 where A38 where A37 where A36 where A35 where A34 where A33 where A32 where A31 where A30 where A29 where A28 where A27 where A26 where A25 where A24 where A23 where A22 where A21 where A20 where A19 where A18 where A17 where A16 where A15 where A14 where A13 where A12 where A11 where A10 where A9 where A8 where A7 where A6 where A5 where A4 where A3 where A2 where A1}, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40} where A40 where A39 where A38 where A37 where A36 where A35 where A34 where A33 where A32 where A31 where A30 where A29 where A28 where A27 where A26 where A25 where A24 where A23 where A22 where A21 where A20 where A19 where A18 where A17 where A16 where A15 where A14 where A13 where A12 where A11 where A10 where A9 where A8 where A7 where A6 where A5 where A4 where A3 where A2 where A1, + Bottom) + +let A = Tuple{Any, Type{Ref{_A}} where _A}, + B = Tuple{Type{T}, Type{<:Union{Ref{T}, T}}} where T, + I = typeintersect(A, B) + @test I != Union{} + # TODO: this intersection result is still too narrow + @test_broken Tuple{Type{Ref{Integer}}, Type{Ref{Integer}}} <: I +end + +@testintersect(Tuple{Type{T}, T} where T<:(Tuple{Vararg{_A, _B}} where _B where _A), + Tuple{Type{Tuple{Vararg{_A, N}} where _A<:F}, Pair{N, F}} where F where N, + Bottom)